From 18fdc7d6c44a2a87776e8264ca5626e556cdc9b9 Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Mon, 16 Dec 2024 22:26:58 +0100 Subject: [PATCH 01/11] add tags --- covas_mobile/lib/pages/ListItemMenu.dart | 88 ++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/covas_mobile/lib/pages/ListItemMenu.dart b/covas_mobile/lib/pages/ListItemMenu.dart index 3614d9c..512bc18 100644 --- a/covas_mobile/lib/pages/ListItemMenu.dart +++ b/covas_mobile/lib/pages/ListItemMenu.dart @@ -14,6 +14,7 @@ import '../variable/globals.dart' as globals; import 'package:permission_handler/permission_handler.dart'; import "Camera.dart"; import 'package:camera/camera.dart'; +import 'package:textfield_tags/textfield_tags.dart'; void main() { initializeDateFormatting("fr_FR", null).then((_) => runApp(const MyApp())); @@ -50,6 +51,7 @@ class _MyHomePageState extends State { TextEditingController startDatepicker = TextEditingController(); TextEditingController endDatepicker = TextEditingController(); TextEditingController inputItem = TextEditingController(); + final _stringTagController = StringTagController(); bool showDateFields = false; // State to toggle date fields bool showArrow = true; @@ -533,6 +535,91 @@ class _MyHomePageState extends State { ); } + TextFieldTags _buildTagsField() { + return TextFieldTags( + textfieldTagsController: _stringTagController, + initialTags: [], + textSeparators: const [' ', ','], + validator: (String tag) { + if (_stringTagController.getTags!.contains(tag)) { + return 'Tu as deja rentre ce tag'; + } + return null; + }, + inputFieldBuilder: (context, inputFieldValues) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + controller: inputFieldValues.textEditingController, + focusNode: inputFieldValues.focusNode, + onChanged: inputFieldValues.onTagChanged, + onSubmitted: inputFieldValues.onTagSubmitted, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: 'Tags', + hintText: + inputFieldValues.tags.isNotEmpty ? '' : "Enter tag...", + errorText: inputFieldValues.error, + prefixIcon: inputFieldValues.tags.isNotEmpty + ? SingleChildScrollView( + controller: inputFieldValues.tagScrollController, + scrollDirection: Axis.vertical, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Wrap( + runSpacing: 4.0, + spacing: 4.0, + children: inputFieldValues.tags.map((String tag) { + return Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular(20.0), + ), + color: Colors.blue, + ), + margin: const EdgeInsets.symmetric( + horizontal: 5.0), + padding: const EdgeInsets.symmetric( + horizontal: 10.0, vertical: 5.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + child: Text( + '$tag', + style: const TextStyle( + color: Colors.white), + ), + onTap: () { + //print("$tag selected"); + }, + ), + const SizedBox(width: 4.0), + InkWell( + child: const Icon( + Icons.cancel, + size: 14.0, + color: Color.fromARGB( + 255, 233, 233, 233), + ), + onTap: () { + inputFieldValues.onTagRemoved(tag); + }, + ) + ], + ), + ); + }).toList()), + ), + ) + : null, + ), + ), + ); + }); + } + Padding _buildItemZoneSearchField() { return Padding( padding: const EdgeInsets.all(8.0), @@ -633,6 +720,7 @@ class _MyHomePageState extends State { Flexible(child: _buildDateField("start")), Flexible(child: _buildDateField("end")) ]), + if ((showDateFields) && (showArrow)) _buildTagsField(), if (showDateFields) _buildGeographicalZoneSearchField(), if (showArrow) IconButton( From eadf07177b99c2f1b3823d65928d12c36f85b971 Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Fri, 20 Dec 2024 22:44:05 +0100 Subject: [PATCH 02/11] simple textfield --- covas_mobile/lib/pages/ListItemMenu.dart | 131 +++++++++-------------- 1 file changed, 48 insertions(+), 83 deletions(-) diff --git a/covas_mobile/lib/pages/ListItemMenu.dart b/covas_mobile/lib/pages/ListItemMenu.dart index 512bc18..be13061 100644 --- a/covas_mobile/lib/pages/ListItemMenu.dart +++ b/covas_mobile/lib/pages/ListItemMenu.dart @@ -51,7 +51,7 @@ class _MyHomePageState extends State { TextEditingController startDatepicker = TextEditingController(); TextEditingController endDatepicker = TextEditingController(); TextEditingController inputItem = TextEditingController(); - final _stringTagController = StringTagController(); + TextEditingController inputTags = TextEditingController(); bool showDateFields = false; // State to toggle date fields bool showArrow = true; @@ -535,89 +535,54 @@ class _MyHomePageState extends State { ); } - TextFieldTags _buildTagsField() { - return TextFieldTags( - textfieldTagsController: _stringTagController, - initialTags: [], - textSeparators: const [' ', ','], - validator: (String tag) { - if (_stringTagController.getTags!.contains(tag)) { - return 'Tu as deja rentre ce tag'; - } - return null; - }, - inputFieldBuilder: (context, inputFieldValues) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: TextField( - controller: inputFieldValues.textEditingController, - focusNode: inputFieldValues.focusNode, - onChanged: inputFieldValues.onTagChanged, - onSubmitted: inputFieldValues.onTagSubmitted, + Padding _buildTagsField() { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + TextField( + controller: inputTags, decoration: InputDecoration( + labelText: 'Search by tags', border: OutlineInputBorder(), - labelText: 'Tags', - hintText: - inputFieldValues.tags.isNotEmpty ? '' : "Enter tag...", - errorText: inputFieldValues.error, - prefixIcon: inputFieldValues.tags.isNotEmpty - ? SingleChildScrollView( - controller: inputFieldValues.tagScrollController, - scrollDirection: Axis.vertical, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Wrap( - runSpacing: 4.0, - spacing: 4.0, - children: inputFieldValues.tags.map((String tag) { - return Container( - decoration: const BoxDecoration( - borderRadius: BorderRadius.all( - Radius.circular(20.0), - ), - color: Colors.blue, - ), - margin: const EdgeInsets.symmetric( - horizontal: 5.0), - padding: const EdgeInsets.symmetric( - horizontal: 10.0, vertical: 5.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - InkWell( - child: Text( - '$tag', - style: const TextStyle( - color: Colors.white), - ), - onTap: () { - //print("$tag selected"); - }, - ), - const SizedBox(width: 4.0), - InkWell( - child: const Icon( - Icons.cancel, - size: 14.0, - color: Color.fromARGB( - 255, 233, 233, 233), - ), - onTap: () { - inputFieldValues.onTagRemoved(tag); - }, - ) - ], - ), - ); - }).toList()), - ), - ) - : null, + suffixIcon: inputItem.text.isEmpty + ? null + : IconButton( + icon: const Icon(Icons.clear), + onPressed: () { + setState(() { + inputItem.clear(); + itemName = ''; // Reset the geographical zone state + suggestionsItem.clear(); + showDateFields = true; + showArrow = true; + }); + fetchPostsByLocation(); + }, + ), ), - ), - ); - }); + onChanged: (value) { + if (value.isNotEmpty) { + setState(() { + itemName = value; + searchSuggestionsByItem(value); + }); + } else { + setState(() { + showDateFields = true; + showArrow = true; // Optionally clear suggestions + inputItem.clear(); // Clear the text field + itemName = ''; // Reset the geographical zone state + suggestionsItem.clear(); + + /// Clear the filted posts + }); + fetchPostsByLocation(); + } + }), + ], + ), + ); } Padding _buildItemZoneSearchField() { @@ -654,11 +619,11 @@ class _MyHomePageState extends State { }); } else { setState(() { + showDateFields = true; + showArrow = true; // Optionally clear suggestions inputItem.clear(); // Clear the text field itemName = ''; // Reset the geographical zone state suggestionsItem.clear(); - showDateFields = true; - showArrow = true; // Optionally clear suggestions /// Clear the filted posts }); From f267e3ede9c0d8d87a7108617ba7061e5570d4ac Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Sat, 21 Dec 2024 14:15:49 +0100 Subject: [PATCH 03/11] add inputtext simple --- covas_mobile/lib/pages/ListItemMenu.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/covas_mobile/lib/pages/ListItemMenu.dart b/covas_mobile/lib/pages/ListItemMenu.dart index be13061..49bdea1 100644 --- a/covas_mobile/lib/pages/ListItemMenu.dart +++ b/covas_mobile/lib/pages/ListItemMenu.dart @@ -56,6 +56,8 @@ class _MyHomePageState extends State { bool showDateFields = false; // State to toggle date fields bool showArrow = true; bool showInputSearch = true; + bool showInputGeo = true; + bool showInputTag = true; // Fetching events from API static Future> getPosts() async { PermissionStatus status = await Permission.location.status; @@ -678,6 +680,7 @@ class _MyHomePageState extends State { body: Column( children: [ if (showInputSearch) _buildItemZoneSearchField(), + if ((showDateFields) && (showInputTag)) _buildTagsField(), if ((showDateFields) && (showArrow)) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -685,8 +688,8 @@ class _MyHomePageState extends State { Flexible(child: _buildDateField("start")), Flexible(child: _buildDateField("end")) ]), - if ((showDateFields) && (showArrow)) _buildTagsField(), - if (showDateFields) _buildGeographicalZoneSearchField(), + if ((showDateFields) && (showInputGeo)) + _buildGeographicalZoneSearchField(), if (showArrow) IconButton( onPressed: () { From e4836ac4ebb995a5742897177446803caaa4e074 Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Sat, 21 Dec 2024 19:06:37 +0100 Subject: [PATCH 04/11] manage show input --- covas_mobile/lib/pages/ListItemMenu.dart | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/covas_mobile/lib/pages/ListItemMenu.dart b/covas_mobile/lib/pages/ListItemMenu.dart index 49bdea1..87f23bf 100644 --- a/covas_mobile/lib/pages/ListItemMenu.dart +++ b/covas_mobile/lib/pages/ListItemMenu.dart @@ -268,6 +268,7 @@ class _MyHomePageState extends State { if (suggestions.isNotEmpty) { showArrow = false; showInputSearch = false; + showInputTag = false; } }); } else { @@ -468,6 +469,7 @@ class _MyHomePageState extends State { showInputSearch = true; // Optionally clear suggestions /// Clear the filtered posts + showInputTag = true; }); fetchPostsByLocation(); }, @@ -489,6 +491,7 @@ class _MyHomePageState extends State { suggestions.clear(); // Optionally clear suggestions showArrow = true; showInputSearch = true; + showInputTag = true; /// Clear the filted posts }); @@ -521,6 +524,7 @@ class _MyHomePageState extends State { suggestions.clear(); showArrow = true; showInputSearch = true; + showInputTag = true; }); SharedPreferences prefs = await SharedPreferences.getInstance(); @@ -553,11 +557,7 @@ class _MyHomePageState extends State { icon: const Icon(Icons.clear), onPressed: () { setState(() { - inputItem.clear(); - itemName = ''; // Reset the geographical zone state - suggestionsItem.clear(); - showDateFields = true; - showArrow = true; + inputTags.clear(); }); fetchPostsByLocation(); }, @@ -565,17 +565,9 @@ class _MyHomePageState extends State { ), onChanged: (value) { if (value.isNotEmpty) { - setState(() { - itemName = value; - searchSuggestionsByItem(value); - }); } else { setState(() { - showDateFields = true; - showArrow = true; // Optionally clear suggestions - inputItem.clear(); // Clear the text field - itemName = ''; // Reset the geographical zone state - suggestionsItem.clear(); + inputTags.clear(); // Clear the text field /// Clear the filted posts }); From 53a60a581a8e51bd176ee1fbe9e3db9680e4a625 Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Sat, 21 Dec 2024 20:19:33 +0100 Subject: [PATCH 05/11] fix tags input --- covas_mobile/lib/pages/ListItemMenu.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/covas_mobile/lib/pages/ListItemMenu.dart b/covas_mobile/lib/pages/ListItemMenu.dart index 87f23bf..e68fd8c 100644 --- a/covas_mobile/lib/pages/ListItemMenu.dart +++ b/covas_mobile/lib/pages/ListItemMenu.dart @@ -551,7 +551,7 @@ class _MyHomePageState extends State { decoration: InputDecoration( labelText: 'Search by tags', border: OutlineInputBorder(), - suffixIcon: inputItem.text.isEmpty + suffixIcon: inputTags.text.isEmpty ? null : IconButton( icon: const Icon(Icons.clear), From ba8db9fb4c23383ffb8c00f97a45ad16edf313cd Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Sun, 22 Dec 2024 16:00:10 +0100 Subject: [PATCH 06/11] add suggestion for inputtags --- covas_mobile/lib/pages/ListItemMenu.dart | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/covas_mobile/lib/pages/ListItemMenu.dart b/covas_mobile/lib/pages/ListItemMenu.dart index e68fd8c..b4fcfd6 100644 --- a/covas_mobile/lib/pages/ListItemMenu.dart +++ b/covas_mobile/lib/pages/ListItemMenu.dart @@ -47,6 +47,7 @@ class _MyHomePageState extends State { String query = ''; List> suggestions = []; List> suggestionsItem = []; + List> suggestionsTags = []; TextEditingController inputGeo = TextEditingController(); TextEditingController startDatepicker = TextEditingController(); TextEditingController endDatepicker = TextEditingController(); @@ -358,6 +359,33 @@ class _MyHomePageState extends State { } } + Future searchSuggestionsByTag(String input) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + var accessToken = prefs.getString("access_token") ?? ""; + + if (accessToken.isNotEmpty) { + var url = Uri.parse("${globals.api}/tags?name=${input}"); + final response = await http.get(url, headers: { + "Content-Type": "application/json", + HttpHeaders.cookieHeader: "access_token=$accessToken" + }); + + if (response.statusCode == 200) { + final data = json.decode(utf8.decode(response.bodyBytes)); + setState(() { + suggestionsTags = (data as List) + .map((feature) => {'name': feature['name']}) + .toList(); + if (suggestionsItem.isNotEmpty) { + showDateFields = false; + showArrow = false; + } + }); + print("status code : ${response.statusCode}"); + } + } + } + Future fetchPostsByLocation() async { SharedPreferences prefs = await SharedPreferences.getInstance(); var accessToken = prefs.getString("access_token") ?? ""; From 03f3e1c55b545d6b5766308f08f926cc82d12c14 Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Sun, 22 Dec 2024 22:11:20 +0100 Subject: [PATCH 07/11] =?UTF-8?q?add=20suggestion=20item=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- covas_mobile/lib/pages/ListItemMenu.dart | 33 +- covas_mobile/lib/pages/UpdateEventImage.dart | 338 +++++++++---------- 2 files changed, 183 insertions(+), 188 deletions(-) diff --git a/covas_mobile/lib/pages/ListItemMenu.dart b/covas_mobile/lib/pages/ListItemMenu.dart index b4fcfd6..1bf80a2 100644 --- a/covas_mobile/lib/pages/ListItemMenu.dart +++ b/covas_mobile/lib/pages/ListItemMenu.dart @@ -44,6 +44,7 @@ class _MyHomePageState extends State { List filteredPosts = []; String geographicalZone = ''; String itemName = ''; + String itemTags = ''; String query = ''; List> suggestions = []; List> suggestionsItem = []; @@ -376,8 +377,9 @@ class _MyHomePageState extends State { suggestionsTags = (data as List) .map((feature) => {'name': feature['name']}) .toList(); - if (suggestionsItem.isNotEmpty) { - showDateFields = false; + if (suggestionsTags.isNotEmpty) { + showInputGeo = false; + showInputSearch = false; showArrow = false; } }); @@ -602,6 +604,33 @@ class _MyHomePageState extends State { fetchPostsByLocation(); } }), + if (suggestionsTags.isNotEmpty) + Container( + height: 200, + decoration: BoxDecoration( + border: Border.all(color: Colors.blue), + borderRadius: BorderRadius.circular(8), + ), + child: ListView.builder( + shrinkWrap: true, + itemCount: suggestions.length, + itemBuilder: (context, index) { + return ListTile( + title: Text(suggestionsTags[index]['name']), + onTap: () async { + setState(() { + itemTags = suggestionsTags[index]['name']; + inputTags.text = itemTags; + suggestionsTags.clear(); + showArrow = true; + showInputSearch = true; + showInputGeo = true; + }); + }, + ); + }, + ), + ), ], ), ); diff --git a/covas_mobile/lib/pages/UpdateEventImage.dart b/covas_mobile/lib/pages/UpdateEventImage.dart index bfadf0d..1f544ca 100644 --- a/covas_mobile/lib/pages/UpdateEventImage.dart +++ b/covas_mobile/lib/pages/UpdateEventImage.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:http/http.dart' as http; +import 'package:uuid/uuid.dart'; import 'package:intl/intl.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:textfield_tags/textfield_tags.dart'; @@ -138,6 +139,7 @@ class _UpdateeventImageState extends State } Future _updateEvent(BuildContext context) async { + // Gather inputs var name = inputName.text; var place = inputGeo.text; var description = inputDesc.text; @@ -152,177 +154,138 @@ class _UpdateeventImageState extends State var startDate = "${startDateFormat}T${startTimepicker.text.replaceAll('-', ':')}"; var endDate = "${endDateFormat}T${endTimepicker.text.replaceAll('-', ':')}"; - if (startDateCompare.isAfter(dateNow)) { - SharedPreferences prefs = await SharedPreferences.getInstance(); - var accessToken = prefs.getString("access_token") ?? ""; - if (accessToken.isNotEmpty) { - try { - await dotenv.load(); - final mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? ''; - print("place non encoded : ${place}"); - final url = - 'https://api.mapbox.com/geocoding/v5/mapbox.places/${place}.json?access_token=${mapboxAccessToken}&types=poi,address,place'; - var encoded = Uri.encodeFull(url); - print("encoded : ${encoded}"); - final response = await http.get(Uri.parse(encoded)); - - if (response.statusCode == 200) { - final data = json.decode(response.body); - print("data : ${data}"); - - if (data['features'].isNotEmpty) { - place = data['features'][0]['place_name']; - final coordinates = - data['features'][0]['geometry']['coordinates']; - final longitude = coordinates[0]; // Longitude - final latitude = coordinates[1]; // Latitude - var urlGet = Uri.parse( - "${globals.api}/events/search?item=${name}&date_event=${startDate}"); - - var responseGet = await http.get(urlGet, headers: { - HttpHeaders.cookieHeader: 'access_token=${accessToken}' - }); - if (responseGet.statusCode == 200) { - var events = jsonDecode(utf8.decode(responseGet.bodyBytes)); - print("reponse http : ${events.length}"); - if (events.length == 0) { - urlGet = Uri.parse( - "${globals.api}/events/search?min_lat=$latitude&max_lat=$latitude" - "&min_lon=$longitude&max_lon=$longitude&date_event=${startDate}"); - responseGet = await http.get(urlGet, headers: { - HttpHeaders.cookieHeader: 'access_token=${accessToken}' - }); - if (responseGet.statusCode == 200) { - events = jsonDecode(utf8.decode(responseGet.bodyBytes)); - print("reponse http : ${events.length}"); - if (events.length != 0) { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => - ItemMenu(title: events[0]["id"]))); - return; - } - } - } else { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => ItemMenu(title: events[0]["id"]))); - return; - } - } - - final params = { - 'expiration': '15552000', - 'key': dotenv.env["IMGBB_API_KEY"], - }; - print("Post Img"); - final urlPost = Uri.parse('https://api.imgbb.com/1/upload') - .replace(queryParameters: params); - File image = File(widget.imagePath); - Uint8List _bytes = await image.readAsBytes(); - String _base64String = base64.encode(_bytes); - - final req = http.MultipartRequest('POST', urlPost) - ..fields['image'] = _base64String; - - final stream = await req.send(); - final res = await http.Response.fromStream(stream); - - final status = res.statusCode; - print("code status imgbb ${status}"); - if (status == 200) { - var body = json.decode(utf8.decode(res.bodyBytes)); - String imgUrl = body["data"]["url"]; - - //String credentials = "${pseudo}:${password}"; - //Codec stringToBase64 = utf8.fuse(base64); - //String encoded = stringToBase64.encode(credentials); - var urlPut = Uri.parse("${globals.api}/events"); - var responsePut = await http.put(urlPut, - headers: { - HttpHeaders.cookieHeader: 'access_token=${accessToken}', - HttpHeaders.acceptHeader: - 'application/json, text/plain, */*', - HttpHeaders.contentTypeHeader: 'application/json' - }, - body: jsonEncode({ - 'name': name, - 'place': place, - 'start_date': startDate, - 'end_date': endDate, - 'organizers': organizers, - 'latitude': latitude, - 'longitude': longitude, - 'description': description, - "imgUrl": imgUrl, - "tags": tags - })); - print(responsePut.statusCode); - if ((responsePut.statusCode == 200) || - (responsePut.statusCode == 201)) { - showEventDialog(context, "Evenement ${name} ajoute"); - } else { - var text = ""; - switch (responsePut.statusCode) { - case 400: - { - text = "Requête mal construite"; - } - break; - case 406: - { - text = "Mot de passe incorrect"; - } - break; - case 404: - { - text = "Utilisateur inconnu"; - } - break; - case 403: - { - text = "Utilisateur desactive"; - } - break; - case 410: - { - text = "Token invalide"; - } - break; - case 500: - { - text = "Probleme interne du serveur"; - } - break; - default: - { - text = "Probleme d'authentification inconnu"; - } - break; - } - showErrorDialog(context, text); - } - } else { - print("imgbb error : ${status}"); - } - } else { - showErrorDialog(context, "Aucune donnée geographique"); - } - } else { - showErrorDialog(context, "Mapbox non accessible"); - } - } catch (e) { - showErrorDialog(context, "${e}"); - } - } else { - showErrorDialog(context, "Champ vide"); - } - } else { + if (!startDateCompare.isAfter(dateNow)) { showErrorDialog(context, "Evenement non futur"); + return; } + + SharedPreferences prefs = await SharedPreferences.getInstance(); + var accessToken = prefs.getString("access_token") ?? ""; + + if (accessToken.isEmpty) { + showErrorDialog(context, "Token d'accès manquant"); + return; + } + + try { + await dotenv.load(); + final mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? ''; + + // Searchbox API for geocoding the place (No session token) + final searchboxUrl = + Uri.parse('https://api.mapbox.com/search/searchbox/v1/retrieve/') + .replace(queryParameters: { + 'access_token': mapboxAccessToken, + 'query': place, + 'limit': '1', + 'types': 'place,address', + }); + + final searchboxResponse = await http.get(searchboxUrl); + + if (searchboxResponse.statusCode != 200) { + showErrorDialog(context, "Erreur lors de la géocodage avec Searchbox"); + return; + } + + final searchboxData = json.decode(searchboxResponse.body); + if (searchboxData['features'].isEmpty) { + showErrorDialog(context, "Lieu introuvable"); + return; + } + + // Extract place details from the searchbox response + final firstFeature = searchboxData['features'][0]; + place = firstFeature['place_name']; + final coordinates = firstFeature['geometry']['coordinates']; + final longitude = coordinates[0]; + final latitude = coordinates[1]; + + // Check if a similar event exists + final eventsUrl = Uri.parse( + "${globals.api}/events/search?item=$name&date_event=$startDate"); + final eventsResponse = await http.get(eventsUrl, headers: { + HttpHeaders.cookieHeader: 'access_token=$accessToken', + }); + + if (eventsResponse.statusCode == 200) { + final events = json.decode(utf8.decode(eventsResponse.bodyBytes)); + if (events.isNotEmpty) { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ItemMenu(title: events[0]["id"]), + ), + ); + return; + } + } + + // Upload image to imgbb + final imgbbUrl = Uri.parse( + 'https://api.imgbb.com/1/upload?expiration=15552000&key=${dotenv.env["IMGBB_API_KEY"]}'); + File image = File(widget.imagePath); + Uint8List imageBytes = await image.readAsBytes(); + String base64Image = base64.encode(imageBytes); + + final imgbbRequest = http.MultipartRequest('POST', imgbbUrl) + ..fields['image'] = base64Image; + final imgbbResponse = + await http.Response.fromStream(await imgbbRequest.send()); + + if (imgbbResponse.statusCode != 200) { + showErrorDialog(context, "Erreur lors de l'upload d'image"); + return; + } + + final imgbbData = json.decode(imgbbResponse.body); + final imgUrl = imgbbData['data']['url']; + + // Create or update the event + final eventUrl = Uri.parse("${globals.api}/events"); + final eventResponse = await http.put( + eventUrl, + headers: { + HttpHeaders.cookieHeader: 'access_token=$accessToken', + HttpHeaders.acceptHeader: 'application/json, text/plain, */*', + HttpHeaders.contentTypeHeader: 'application/json', + }, + body: jsonEncode({ + 'name': name, + 'place': place, + 'start_date': startDate, + 'end_date': endDate, + 'organizers': organizers, + 'latitude': latitude, + 'longitude': longitude, + 'description': description, + 'imgUrl': imgUrl, + 'tags': tags, + }), + ); + + if (eventResponse.statusCode == 200 || eventResponse.statusCode == 201) { + showEventDialog(context, "Événement $name ajouté"); + } else { + handleHttpError(eventResponse.statusCode, context); + } + } catch (e) { + showErrorDialog(context, "Erreur: ${e.toString()}"); + } + } + +// Utility function to handle HTTP errors + void handleHttpError(int statusCode, BuildContext context) { + final errorMessages = { + 400: "Requête mal construite", + 403: "Utilisateur désactivé", + 404: "Utilisateur inconnu", + 406: "Mot de passe incorrect", + 410: "Token invalide", + 500: "Problème interne du serveur", + }; + showErrorDialog(context, errorMessages[statusCode] ?? "Erreur inconnue"); } void start() async { @@ -358,25 +321,32 @@ class _UpdateeventImageState extends State } Future searchSuggestions(String input) async { + var uuid = Uuid(); + String sessionToken = uuid.v4(); await dotenv.load(fileName: ".env"); // Load .env file final mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? ''; - final url = - 'https://api.mapbox.com/geocoding/v5/mapbox.places/${input}.json?access_token=${mapboxAccessToken}&types=poi,address,place'; - var encoded = Uri.encodeFull(url); - final response = await http.get(Uri.parse(encoded)); - print("response code suggesttion : ${response.statusCode}"); + + // Define the Searchbox API URL + final searchboxUrl = Uri.parse( + 'https://api.mapbox.com/search/searchbox/v1/suggest?q=${input}&limit=5&language=en&types=place,poi,address&session_token=${sessionToken}&access_token=${mapboxAccessToken}'); + + // Perform the request + final response = await http.get(searchboxUrl); + print("response code suggestion: ${response.statusCode}"); if (response.statusCode == 200) { final data = json.decode(response.body); - print("data suggestion : ${data}"); + print("data suggestion: ${data}"); + setState(() { - suggestions = (data['features'] as List) + // Map the results to extract name and full_address + suggestions = (data['suggestions'] as List) .map((feature) => { - 'place_name': feature['place_name'], - 'text': feature['text'], - 'geometry': feature[ - 'geometry'], // Include geometry for latitude/longitude + 'name': feature['name'], + 'full_address': feature[ + 'place_formatted'], // Adjusted to match the data structure + 'context': feature['context'], // Additional context data }) .toList(); }); @@ -426,17 +396,13 @@ class _UpdateeventImageState extends State itemCount: suggestions.length, itemBuilder: (context, index) { return ListTile( - title: Text(suggestions[index]['text']), - subtitle: Text(suggestions[index]['place_name']), + title: Text(suggestions[index]['name']), + subtitle: Text(suggestions[index]['full_address']), onTap: () async { print("suggestion tapped : ${suggestions[index]}"); - final latitude = - suggestions[index]['geometry']['coordinates'][1]; - final longitude = - suggestions[index]['geometry']['coordinates'][0]; setState(() { - geographicalZone = suggestions[index]['text']; + geographicalZone = suggestions[index]['name']; inputGeo.text = geographicalZone; suggestions.clear(); }); From 43124d9cb9d9677fc174704f97b2591d5e1f6661 Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Sun, 22 Dec 2024 22:37:17 +0100 Subject: [PATCH 08/11] add list adress from v6 mapbox and searchbox --- covas_mobile/lib/pages/UpdateEventImage.dart | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/covas_mobile/lib/pages/UpdateEventImage.dart b/covas_mobile/lib/pages/UpdateEventImage.dart index 1f544ca..5532b92 100644 --- a/covas_mobile/lib/pages/UpdateEventImage.dart +++ b/covas_mobile/lib/pages/UpdateEventImage.dart @@ -173,14 +173,8 @@ class _UpdateeventImageState extends State final mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? ''; // Searchbox API for geocoding the place (No session token) - final searchboxUrl = - Uri.parse('https://api.mapbox.com/search/searchbox/v1/retrieve/') - .replace(queryParameters: { - 'access_token': mapboxAccessToken, - 'query': place, - 'limit': '1', - 'types': 'place,address', - }); + final searchboxUrl = Uri.parse( + 'https://api.mapbox.com/search/geocode/v6/forward?access_token=${mapboxAccessToken}&q=${place}'); final searchboxResponse = await http.get(searchboxUrl); @@ -197,7 +191,7 @@ class _UpdateeventImageState extends State // Extract place details from the searchbox response final firstFeature = searchboxData['features'][0]; - place = firstFeature['place_name']; + place = firstFeature['properties']["full_address"]; final coordinates = firstFeature['geometry']['coordinates']; final longitude = coordinates[0]; final latitude = coordinates[1]; @@ -345,8 +339,7 @@ class _UpdateeventImageState extends State .map((feature) => { 'name': feature['name'], 'full_address': feature[ - 'place_formatted'], // Adjusted to match the data structure - 'context': feature['context'], // Additional context data + 'full_address'] // Adjusted to match the data structure }) .toList(); }); @@ -402,7 +395,7 @@ class _UpdateeventImageState extends State print("suggestion tapped : ${suggestions[index]}"); setState(() { - geographicalZone = suggestions[index]['name']; + geographicalZone = suggestions[index]['full_address']; inputGeo.text = geographicalZone; suggestions.clear(); }); From c5de20d64bf56ad9dda53c46e18445270517f9a2 Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Wed, 25 Dec 2024 22:18:02 +0100 Subject: [PATCH 09/11] change place api mapbox to google place api --- covas_mobile/lib/pages/UpdateEventImage.dart | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/covas_mobile/lib/pages/UpdateEventImage.dart b/covas_mobile/lib/pages/UpdateEventImage.dart index 5532b92..df19763 100644 --- a/covas_mobile/lib/pages/UpdateEventImage.dart +++ b/covas_mobile/lib/pages/UpdateEventImage.dart @@ -315,15 +315,15 @@ class _UpdateeventImageState extends State } Future searchSuggestions(String input) async { - var uuid = Uuid(); + String sessionToken = uuid.v4(); await dotenv.load(fileName: ".env"); // Load .env file - final mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? ''; + final ApiTokenGoogle = dotenv.env['GEMINI_API_KEY'] ?? ''; // Define the Searchbox API URL final searchboxUrl = Uri.parse( - 'https://api.mapbox.com/search/searchbox/v1/suggest?q=${input}&limit=5&language=en&types=place,poi,address&session_token=${sessionToken}&access_token=${mapboxAccessToken}'); + 'https://maps.googleapis.com/maps/api/place/textsearch/json?query=${input}&key=${ApiTokenGoogle}'); // Perform the request final response = await http.get(searchboxUrl); @@ -335,11 +335,11 @@ class _UpdateeventImageState extends State setState(() { // Map the results to extract name and full_address - suggestions = (data['suggestions'] as List) + suggestions = (data['results'] as List) .map((feature) => { 'name': feature['name'], - 'full_address': feature[ - 'full_address'] // Adjusted to match the data structure + 'formatted_address': feature[ + 'formatted_address'] // Adjusted to match the data structure }) .toList(); }); @@ -390,12 +390,12 @@ class _UpdateeventImageState extends State itemBuilder: (context, index) { return ListTile( title: Text(suggestions[index]['name']), - subtitle: Text(suggestions[index]['full_address']), + subtitle: Text(suggestions[index]['formatted_address']), onTap: () async { print("suggestion tapped : ${suggestions[index]}"); setState(() { - geographicalZone = suggestions[index]['full_address']; + geographicalZone = suggestions[index]['formatted_address']; inputGeo.text = geographicalZone; suggestions.clear(); }); From 48c785c5865b43b85126f998efd313069f5e2ca3 Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Fri, 27 Dec 2024 23:31:49 +0100 Subject: [PATCH 10/11] change mapbox place to google place api --- covas_mobile/lib/pages/EditEvent.dart | 29 ++++++++++---------- covas_mobile/lib/pages/UpdateEventImage.dart | 25 ++++++++--------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/covas_mobile/lib/pages/EditEvent.dart b/covas_mobile/lib/pages/EditEvent.dart index fedc2bb..3f95af3 100644 --- a/covas_mobile/lib/pages/EditEvent.dart +++ b/covas_mobile/lib/pages/EditEvent.dart @@ -180,24 +180,23 @@ class _EditEventState extends State if (accessToken.isNotEmpty) { try { await dotenv.load(); - final mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? ''; - print("place non encoded : ${place}"); - final url = - 'https://api.mapbox.com/geocoding/v5/mapbox.places/${place}.json?access_token=${mapboxAccessToken}&types=poi,address,place'; - var encoded = Uri.encodeFull(url); - print("encoded : ${encoded}"); - final response = await http.get(Uri.parse(encoded)); + final ApiTokenGoogle = dotenv.env['PLACE_API_KEY'] ?? ''; + // Searchbox API for geocoding the place (No session token) + final searchboxUrl = Uri.parse( + 'https://maps.googleapis.com/maps/api/place/textsearch/json?query=${place}&key=${ApiTokenGoogle}'); - if (response.statusCode == 200) { - final data = json.decode(response.body); + // Perform the request + final searchboxResponse = await http.get(searchboxUrl); + + if (searchboxResponse.statusCode == 200) { + final data = json.decode(searchboxResponse.body); print("data : ${data}"); - if (data['features'].isNotEmpty) { - place = data['features'][0]['place_name']; - final coordinates = - data['features'][0]['geometry']['coordinates']; - final longitude = coordinates[0]; // Longitude - final latitude = coordinates[1]; // Latitude + if (data['results'].isNotEmpty) { + place = data['results'][0]['formatted_address']; + final coordinates = data['results'][0]['geometry']['location']; + final longitude = coordinates["lng"]; // Longitude + final latitude = coordinates["lat"]; // Latitude var urlGet = Uri.parse( "${globals.api}/events/search?item=${name}&date_event=${startDate}&min_lat=$latitude&max_lat=$latitude" "&min_lon=$longitude&max_lon=$longitude"); diff --git a/covas_mobile/lib/pages/UpdateEventImage.dart b/covas_mobile/lib/pages/UpdateEventImage.dart index df19763..83a1c85 100644 --- a/covas_mobile/lib/pages/UpdateEventImage.dart +++ b/covas_mobile/lib/pages/UpdateEventImage.dart @@ -170,12 +170,12 @@ class _UpdateeventImageState extends State try { await dotenv.load(); - final mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? ''; - + final ApiTokenGoogle = dotenv.env['PLACE_API_KEY'] ?? ''; // Searchbox API for geocoding the place (No session token) final searchboxUrl = Uri.parse( - 'https://api.mapbox.com/search/geocode/v6/forward?access_token=${mapboxAccessToken}&q=${place}'); + 'https://maps.googleapis.com/maps/api/place/textsearch/json?query=${place}&key=${ApiTokenGoogle}'); + // Perform the request final searchboxResponse = await http.get(searchboxUrl); if (searchboxResponse.statusCode != 200) { @@ -184,17 +184,17 @@ class _UpdateeventImageState extends State } final searchboxData = json.decode(searchboxResponse.body); - if (searchboxData['features'].isEmpty) { + if (searchboxData['results'].isEmpty) { showErrorDialog(context, "Lieu introuvable"); return; } // Extract place details from the searchbox response - final firstFeature = searchboxData['features'][0]; - place = firstFeature['properties']["full_address"]; - final coordinates = firstFeature['geometry']['coordinates']; - final longitude = coordinates[0]; - final latitude = coordinates[1]; + final firstFeature = searchboxData['results'][0]; + place = firstFeature["formatted_address"]; + final coordinates = firstFeature['geometry']['location']; + final longitude = coordinates["lng"]; + final latitude = coordinates["lat"]; // Check if a similar event exists final eventsUrl = Uri.parse( @@ -315,11 +315,9 @@ class _UpdateeventImageState extends State } Future searchSuggestions(String input) async { - - String sessionToken = uuid.v4(); await dotenv.load(fileName: ".env"); // Load .env file - final ApiTokenGoogle = dotenv.env['GEMINI_API_KEY'] ?? ''; + final ApiTokenGoogle = dotenv.env['PLACE_API_KEY'] ?? ''; // Define the Searchbox API URL final searchboxUrl = Uri.parse( @@ -395,7 +393,8 @@ class _UpdateeventImageState extends State print("suggestion tapped : ${suggestions[index]}"); setState(() { - geographicalZone = suggestions[index]['formatted_address']; + geographicalZone = + suggestions[index]['formatted_address']; inputGeo.text = geographicalZone; suggestions.clear(); }); From 0df538ef4631fe734751dbb3488aa0cd567155ee Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Sat, 28 Dec 2024 14:47:39 +0100 Subject: [PATCH 11/11] =?UTF-8?q?recherche=20par=20tags=20termin=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- covas_mobile/lib/pages/ListItemMenu.dart | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/covas_mobile/lib/pages/ListItemMenu.dart b/covas_mobile/lib/pages/ListItemMenu.dart index 1bf80a2..7f42243 100644 --- a/covas_mobile/lib/pages/ListItemMenu.dart +++ b/covas_mobile/lib/pages/ListItemMenu.dart @@ -325,6 +325,10 @@ class _MyHomePageState extends State { stringParameter = stringParameter + "&item=${inputItem.text}"; endpoint = "events/search"; } + if (inputTags.text.isNotEmpty) { + stringParameter = stringParameter + "&tags=${inputTags.text}"; + endpoint = "events/search"; + } if (stringParameter.isNotEmpty) { stringParameter = "$stringParameter&$dateParameter"; } else { @@ -370,20 +374,22 @@ class _MyHomePageState extends State { "Content-Type": "application/json", HttpHeaders.cookieHeader: "access_token=$accessToken" }); + print("status code tags : ${response.statusCode}"); if (response.statusCode == 200) { final data = json.decode(utf8.decode(response.bodyBytes)); + print("tags ${data}"); setState(() { suggestionsTags = (data as List) .map((feature) => {'name': feature['name']}) .toList(); + print("suggesttion tag : ${suggestionsTags}"); if (suggestionsTags.isNotEmpty) { showInputGeo = false; showInputSearch = false; showArrow = false; } }); - print("status code : ${response.statusCode}"); } } } @@ -398,6 +404,7 @@ class _MyHomePageState extends State { "Content-Type": "application/json", HttpHeaders.cookieHeader: "access_token=$accessToken" }); + print("status code : ${response.statusCode}"); if (response.statusCode == 200) { final List body = json.decode(utf8.decode(response.bodyBytes)); @@ -595,9 +602,17 @@ class _MyHomePageState extends State { ), onChanged: (value) { if (value.isNotEmpty) { + setState(() { + itemTags = value; + searchSuggestionsByTag(value); + }); } else { setState(() { - inputTags.clear(); // Clear the text field + inputTags.clear(); + showArrow = true; + showInputSearch = true; + showInputGeo = true; // Optionally clear suggestions + itemTags = ''; // Clear the text field /// Clear the filted posts }); @@ -613,7 +628,7 @@ class _MyHomePageState extends State { ), child: ListView.builder( shrinkWrap: true, - itemCount: suggestions.length, + itemCount: suggestionsTags.length, itemBuilder: (context, index) { return ListTile( title: Text(suggestionsTags[index]['name']), @@ -626,6 +641,7 @@ class _MyHomePageState extends State { showInputSearch = true; showInputGeo = true; }); + await fetchPostsByLocation(); }, ); },