From 44a0691e312e11a7aa393d9b37b8ef15d2d73430 Mon Sep 17 00:00:00 2001 From: Valentin CZERYBA Date: Thu, 7 Nov 2024 23:10:03 +0100 Subject: [PATCH] add latitude and longitude --- .../lib/pages/DisplayPictureScreen.dart | 9 +- covas_mobile/lib/pages/ListItemMenu.dart | 52 ++- covas_mobile/lib/pages/UpdateEventImage.dart | 353 ++++++++++-------- 3 files changed, 240 insertions(+), 174 deletions(-) diff --git a/covas_mobile/lib/pages/DisplayPictureScreen.dart b/covas_mobile/lib/pages/DisplayPictureScreen.dart index 0c6d18a..76a5fc3 100644 --- a/covas_mobile/lib/pages/DisplayPictureScreen.dart +++ b/covas_mobile/lib/pages/DisplayPictureScreen.dart @@ -85,11 +85,14 @@ class DisplayPictureScreenState extends State } Future searchEvents(String json, String imagePath) async { - print(json); + print(json.replaceAll("'''json", '').replaceAll("'''", "")); SharedPreferences prefs = await SharedPreferences.getInstance(); - Map jsonData = jsonDecode(json); + Map jsonData = + jsonDecode(json.replaceAll("```json", '').replaceAll("```", "")); + print("json : ${jsonData}"); var name = jsonData["name"]; + print("name : ${name}"); var place = jsonData["place"]; var accessToken = prefs.getString("access_token") ?? ""; @@ -132,7 +135,7 @@ class DisplayPictureScreenState extends State gemini .textAndImage( text: - "Peux-tu donner le nom, la date avec l'année actuelle ou d'une année future proche et le lieu de l'évènement sous format JSON avec les valeurs suivantes : name, address, city, zip_code, country, description, tags (tableau sans espace), organizers (tableau), start_date et end_date sous le format en YYYY-MM-DD HH:mm:ssZ, et sans la présence du mot json dans la chaîne de caractère", + "Peux-tu donner le nom, la date avec l'année actuelle ou d'une année future proche et le lieu de l'évènement sous format JSON (sans le caratère json au début de la chaine de caractère) avec les valeurs suivantes : name, place, description, tags (tableau sans espace), organizers (tableau), start_date et end_date sous le format en YYYY-MM-DD HH:mm:ssZ", images: [file.readAsBytesSync()], modelName: "models/gemini-1.5-pro-latest") .then((value) => searchEvents( diff --git a/covas_mobile/lib/pages/ListItemMenu.dart b/covas_mobile/lib/pages/ListItemMenu.dart index bde1c0f..c280419 100644 --- a/covas_mobile/lib/pages/ListItemMenu.dart +++ b/covas_mobile/lib/pages/ListItemMenu.dart @@ -13,6 +13,8 @@ import 'dart:math'; import 'package:geolocator/geolocator.dart'; import '../variable/globals.dart' as globals; import 'package:permission_handler/permission_handler.dart'; +import "Camera.dart"; +import 'package:camera/camera.dart'; void main() { initializeDateFormatting("fr_FR", null).then((_) => runApp(const MyApp())); @@ -356,6 +358,11 @@ class _MyHomePageState extends State { ); } + Future popCamera() async { + await availableCameras().then((value) => Navigator.push(context, + MaterialPageRoute(builder: (_) => Camera(camera: value.first)))); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -397,6 +404,12 @@ class _MyHomePageState extends State { ), ], ), + floatingActionButton: FloatingActionButton( + onPressed: popCamera, + backgroundColor: Colors.blue, + tooltip: 'Recherche', + child: const Icon(Icons.photo_camera, color: Colors.white), + ), ); } @@ -415,25 +428,26 @@ class _MyHomePageState extends State { } return ListView.separated( - itemCount: displayedPosts.length, - itemBuilder: (context, index) { - final post = displayedPosts[index]; - final startDate = DateTime.parse(post.startDate!); - final date = DateFormat.yMd().format(startDate); - final time = DateFormat.Hm().format(startDate); + itemCount: displayedPosts.length, + itemBuilder: (context, index) { + final post = displayedPosts[index]; + final startDate = DateTime.parse(post.startDate!); + final date = DateFormat.yMd().format(startDate); + final time = DateFormat.Hm().format(startDate); - return ListTile( - title: Text('${post.name!}'), - subtitle: Text('${post.place!}\n${date} ${time}'), - onTap: () { - Navigator.push( - context, - MaterialPageRoute(builder: (_) => ItemMenu(title: post.id!)), - ); - }, - ); - }, - separatorBuilder: (context, index) => Divider(), - ); + return ListTile( + title: Text('${post.name!}'), + subtitle: Text('${post.place!}\n${date} ${time}'), + onTap: () { + Navigator.push( + context, + MaterialPageRoute(builder: (_) => ItemMenu(title: post.id!)), + ); + }, + ); + }, + separatorBuilder: (context, index) { + return Divider(); + }); } } diff --git a/covas_mobile/lib/pages/UpdateEventImage.dart b/covas_mobile/lib/pages/UpdateEventImage.dart index 650aa4c..c3e0bfa 100644 --- a/covas_mobile/lib/pages/UpdateEventImage.dart +++ b/covas_mobile/lib/pages/UpdateEventImage.dart @@ -44,20 +44,21 @@ class UpdateeventImage extends StatefulWidget { class _UpdateeventImageState extends State with ShowErrorDialog, ShowEventDialog { TextEditingController inputName = TextEditingController(); - TextEditingController inputAddress = TextEditingController(); - TextEditingController inputZipCode = TextEditingController(); - - TextEditingController inputCity = TextEditingController(); - TextEditingController inputCountry = TextEditingController(); TextEditingController inputDate = TextEditingController(); TextEditingController inputDesc = TextEditingController(); + TextEditingController inputGeo = TextEditingController(); + TextEditingController startDatepicker = TextEditingController(); TextEditingController startTimepicker = TextEditingController(); TextEditingController endDatepicker = TextEditingController(); TextEditingController endTimepicker = TextEditingController(); final _stringTagController = StringTagController(); + + List> suggestions = []; + String geographicalZone = ""; + List initialTags = []; final _stringOrgaController = StringTagController(); @@ -128,10 +129,7 @@ class _UpdateeventImageState extends State Future _updateEvent(BuildContext context) async { var url = Uri.parse("${globals.api}/token"); var name = inputName.text; - var place = inputAddress.text; - var city = inputCity.text; - var country = inputCountry.text; - var zipCode = inputZipCode.text; + var place = inputGeo.text; var description = inputDesc.text; List tags = List.from(_stringTagController.getTags as List); List organizers = @@ -150,101 +148,118 @@ class _UpdateeventImageState extends State if (accessToken.isNotEmpty) { try { await dotenv.load(); - 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 mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? ''; + final url = + 'https://api.mapbox.com/geocoding/v5/mapbox.places/${place}.json?access_token=${mapboxAccessToken}&proximity=ip'; + final response = await http.get(Uri.parse(url)); - final req = http.MultipartRequest('POST', urlPost) - ..fields['image'] = _base64String; + if (response.statusCode == 200) { + final data = json.decode(response.body); - final stream = await req.send(); - final res = await http.Response.fromStream(stream); + if (data['features'].isNotEmpty) { + final coordinates = data['features'][0]['geometry']['coordinates']; + final longitude = coordinates[0]; // Longitude + final latitude = coordinates[1]; // Latitude - 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"]; + 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); - //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, - 'zip_code': zipCode, - 'country': country, - 'city': city, - 'organizers': organizers, - 'latitude': '0.0', - 'longitude': '0.0', - '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"; + 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; } - 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}"); } - showErrorDialog(context, text); + } else { + showErrorDialog(context, "Aucune donnée geographique"); } } else { - print("imgbb error : ${status}"); + showErrorDialog(context, "Mapbox non accessible"); } } catch (e) { showErrorDialog(context, "${e}"); @@ -255,11 +270,9 @@ class _UpdateeventImageState extends State } void start() async { + print("events : ${widget.events}"); inputName.text = convertNulltoEmptyString(widget.events["name"]); - inputCity.text = convertNulltoEmptyString(widget.events["city"]); - inputAddress.text = convertNulltoEmptyString(widget.events["address"]); - inputZipCode.text = convertNulltoEmptyString(widget.events["zip_code"]); - inputCountry.text = convertNulltoEmptyString(widget.events["country"]); + inputGeo.text = convertNulltoEmptyString(widget.events["place"]); inputDesc.text = convertNulltoEmptyString(widget.events["description"]); DateTime pickedStartDate = @@ -286,6 +299,93 @@ class _UpdateeventImageState extends State return value!.isEmpty ? 'Champ requis' : null; } + Future searchSuggestions(String input) async { + 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}&proximity=ip'; + final response = await http.get(Uri.parse(url)); + + if (response.statusCode == 200) { + final data = json.decode(response.body); + setState(() { + suggestions = (data['features'] as List) + .map((feature) => { + 'place_name': feature['place_name'], + 'geometry': feature[ + 'geometry'], // Include geometry for latitude/longitude + }) + .toList(); + }); + } else { + throw Exception('Failed to load suggestions'); + } + } + + Padding _buildGeographicalZoneSearchField() { + return Padding( + padding: + const EdgeInsets.only(left: 15.0, right: 15.0, top: 15, bottom: 0), + child: Column( + children: [ + TextField( + controller: inputGeo, + decoration: InputDecoration( + labelText: 'Lieu', + border: OutlineInputBorder(), + suffixIcon: IconButton( + icon: const Icon(Icons.clear), + onPressed: () { + setState(() { + inputGeo.clear(); // Clear the text field + geographicalZone = ''; // Reset the geographical zone state + suggestions.clear(); // Optionally clear suggestions + }); + }, + ), + ), + onChanged: (value) { + setState(() { + geographicalZone = value; + searchSuggestions(value); + }); + }, + ), + if (suggestions.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(suggestions[index]['place_name']), + onTap: () async { + final latitude = + suggestions[index]['geometry']['coordinates'][1]; + final longitude = + suggestions[index]['geometry']['coordinates'][0]; + + setState(() { + geographicalZone = suggestions[index]['place_name']; + inputGeo.text = geographicalZone; + suggestions.clear(); + }); + }, + ); + }, + ), + ), + ], + ), + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -323,58 +423,7 @@ class _UpdateeventImageState extends State hintText: 'Modifier le nom de l\'évènement'), ), ), - Padding( - padding: const EdgeInsets.only( - left: 15.0, right: 15.0, top: 15, bottom: 0), - //padding: EdgeInsets.symmetric(horizontal: 15), - child: TextFormField( - controller: inputAddress, - validator: (value) => _validateField(value), - decoration: InputDecoration( - border: OutlineInputBorder(), - labelText: 'Adresse', - hintText: 'Entrer une adresse'), - ), - ), - Padding( - padding: const EdgeInsets.only( - left: 15.0, right: 15.0, top: 15, bottom: 0), - //padding: EdgeInsets.symmetric(horizontal: 15), - child: TextFormField( - controller: inputZipCode, - validator: (value) => _validateField(value), - decoration: InputDecoration( - border: OutlineInputBorder(), - labelText: 'Code postal', - hintText: 'Entrer un code postal'), - ), - ), - Padding( - padding: const EdgeInsets.only( - left: 15.0, right: 15.0, top: 15, bottom: 0), - //padding: EdgeInsets.symmetric(horizontal: 15), - child: TextFormField( - controller: inputCity, - validator: (value) => _validateField(value), - decoration: InputDecoration( - border: OutlineInputBorder(), - labelText: 'Ville', - hintText: 'Entrer une ville'), - ), - ), - Padding( - padding: const EdgeInsets.only( - left: 15.0, right: 15.0, top: 15, bottom: 0), - //padding: EdgeInsets.symmetric(horizontal: 15), - child: TextFormField( - controller: inputCountry, - validator: (value) => _validateField(value), - decoration: InputDecoration( - border: OutlineInputBorder(), - labelText: 'Pays', - hintText: 'Entrer un pays'), - ), - ), + _buildGeographicalZoneSearchField(), Padding( padding: const EdgeInsets.only( left: 15.0, right: 15.0, top: 15, bottom: 0),