import 'package:covas_mobile/pages/CameraEdit.dart'; import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:http/http.dart' as http; import 'package:intl/intl.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:textfield_tags/textfield_tags.dart'; import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import '../classes/events.dart'; import '../classes/MyDrawer.dart'; import 'ItemMenu.dart'; import 'CameraEdit.dart'; import 'package:camera/camera.dart'; import '../classes/alert.dart'; import '../classes/eventAdded.dart'; import '../variable/globals.dart' as globals; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { Events? events; @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: EditEvent( events: events, imgPath: "", ), ); } } class EditEvent extends StatefulWidget { const EditEvent({Key? key, required this.events, required this.imgPath}) : super(key: key); final Events? events; final String imgPath; @override _EditEventState createState() => _EditEventState(); } class _EditEventState extends State with ShowAlertDialog, ShowEventDialog { TextEditingController inputName = 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(); DateTime startDate = DateTime.now(); DateTime endDate = DateTime.now(); List> suggestions = []; String geographicalZone = ""; String imgUrl = ""; List initialTags = []; final _stringOrgaController = StringTagController(); List initialOrga = []; onTapFunctionDatePicker( {required BuildContext context, required String position}) async { DateTime date; if ((startDatepicker.text.isEmpty) || (endDatepicker.text.isEmpty)) { date = DateTime.now(); } else { date = DateTime.parse(formatDate(startDatepicker.text)); if (position == "end") { date = DateTime.parse(formatDate(endDatepicker.text)); } } DateTime? pickedDate = await showDatePicker( context: context, firstDate: date, initialDate: date, lastDate: DateTime(2104)); if (pickedDate == null) return; if (position == "start") { startDatepicker.text = DateFormat("dd/MM/yyyy").format(pickedDate); } if (position == "end") { endDatepicker.text = DateFormat("dd/MM/yyyy").format(pickedDate); } } onTapFunctionTimePicker( {required BuildContext context, required String position}) async { TimeOfDay time; if ((startTimepicker.text.isEmpty) || (endTimepicker.text.isEmpty)) { time = TimeOfDay.now(); } else { DateTime date = new DateTime.now(); date = date.copyWith( hour: int.parse(startTimepicker.text.split(":")[0]), minute: int.parse(startTimepicker.text.split(":")[1])); time = TimeOfDay.fromDateTime(date); if (position == "end") { date = date.copyWith( hour: int.parse(endTimepicker.text.split(":")[0]), minute: int.parse(endTimepicker.text.split(":")[1])); time = TimeOfDay.fromDateTime(date); } } TimeOfDay? pickedDate = await showTimePicker(context: context, initialTime: time); if (pickedDate == null) return; if (position == "start") { startTimepicker.text = pickedDate.format(context); } if (position == "end") { endTimepicker.text = pickedDate.format(context); } } convertNulltoEmptyString(var check) { if (check == null) { return ""; } return check; } convertNulltoArray(List check) { if (check == null) { return []; } return check; } String formatDate(String date) { var splitedDate = date.split("/"); var day = splitedDate[0]; var month = splitedDate[1]; var year = splitedDate[2]; return "${year}-${month}-${day}"; } Future _updateEvent(BuildContext context) async { var name = inputName.text; var place = inputGeo.text; var description = inputDesc.text; List tags = List.from(_stringTagController.getTags as List); List organizers = List.from(_stringOrgaController.getTags as List); var startDateFormat = formatDate(startDatepicker.text); //DateTime startDateCompare = DateTime.parse(startDateFormat); DateTime dateNow = DateTime.now(); var endDateFormat = formatDate(endDatepicker.text); var startDate = "${startDateFormat}T${startTimepicker.text.replaceAll('-', ':')}"; var endDate = "${endDateFormat}T${endTimepicker.text.replaceAll('-', ':')}"; DateTime startDateCompare = DateTime.parse(startDate); if (startDateCompare.isAfter(dateNow)) { SharedPreferences prefs = await SharedPreferences.getInstance(); var accessToken = prefs.getString("access_token") ?? ""; if (accessToken.isNotEmpty) { try { await dotenv.load(); 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}'); // Perform the request final searchboxResponse = await http.get(searchboxUrl); if (searchboxResponse.statusCode == 200) { final data = json.decode(searchboxResponse.body); print("data : ${data}"); 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"); var responseGet = await http.get(urlGet, headers: { HttpHeaders.cookieHeader: 'access_token=${accessToken}' }); if (responseGet.statusCode == 200) { var events = jsonDecode(utf8.decode(responseGet.bodyBytes)); if (events.length > 0) { if (events[0]["id"] != widget.events!.id) { showAlertDialog( context, "Info evenement", "Evenement deja existant"); } return; } } if (widget.imgPath.isNotEmpty) { 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.imgPath); 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) { showAlertDialog(context, "Erreur image", "Image non posté"); return; } var body = json.decode(utf8.decode(res.bodyBytes)); imgUrl = body["data"]["url"]; } var urlPut = Uri.parse("${globals.api}/events/${widget.events!.id}"); 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} modifie"); } 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; } showAlertDialog(context, "Erreur serveur", text); } } else { showAlertDialog( context, "Erreur serveur", "Aucune donnée geographique"); } } else { showAlertDialog(context, "Erreur serveur", "Mapbox non accessible"); } } catch (e) { showAlertDialog(context, "Erreur serveur", "${e}"); } } else { showAlertDialog(context, "Erreur utilisateur", "Champ vide"); } } else { showAlertDialog(context, "Erreur evenement", "Evenement non futur"); } } @override void initState() { inputName.text = widget.events!.name ?? ""; startDatepicker.text = DateFormat("dd/MM/yyyy") .format(DateTime.parse( widget.events!.startDate ?? DateTime.now().toString())) .toString(); startTimepicker.text = DateFormat("HH:mm") .format(DateTime.parse( widget.events!.startDate ?? DateTime.now().toString())) .toString(); endDatepicker.text = DateFormat("dd/MM/yyyy") .format( DateTime.parse(widget.events!.endDate ?? DateTime.now().toString())) .toString(); endTimepicker.text = DateFormat("HH:mm") .format( DateTime.parse(widget.events!.endDate ?? DateTime.now().toString())) .toString(); inputGeo.text = widget.events!.place ?? ""; imgUrl = widget.events!.imgUrl ?? ""; inputDesc.text = widget.events!.description ?? ""; initialTags = List.from(widget.events!.tags as List); initialOrga = List.from(widget.events!.organizers as List); super.initState(); } final _formKey = GlobalKey(); String? _validateField(String? value) { 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}&types=poi,address,place'; var encoded = Uri.encodeFull(url); final response = await http.get(Uri.parse(encoded)); print("response code suggesttion : ${response.statusCode}"); if (response.statusCode == 200) { final data = json.decode(response.body); print("data suggestion : ${data}"); setState(() { suggestions = (data['features'] as List) .map((feature) => { 'place_name': feature['place_name'], 'text': feature['text'], '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]['text']), subtitle: Text(suggestions[index]['place_name']), 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']; inputGeo.text = geographicalZone; suggestions.clear(); }); }, ); }, ), ), ], ), ); } Future popCamera() async { await availableCameras().then((value) => Navigator.push( context, MaterialPageRoute( builder: (_) => CameraEdit(camera: value.first, events: widget.events)))); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, drawer: MyDrawer(), appBar: AppBar( title: Text("Add or Update a event"), backgroundColor: Colors.blue, foregroundColor: Colors.white, ), body: Form( key: _formKey, child: SingleChildScrollView( child: Column( children: [ if (widget.imgPath.isNotEmpty) Padding( padding: const EdgeInsets.only(top: 60.0), child: Center( child: Container( width: 200, height: 150, decoration: BoxDecoration( borderRadius: BorderRadius.circular(100.0)), child: Image.file(File(widget.imgPath))), ), ), if (widget.imgPath.isEmpty) Padding( padding: const EdgeInsets.only(top: 60.0), child: Image.network( imgUrl, width: MediaQuery.of(context).size.width * 0.5, // 50% of screen width height: MediaQuery.of(context).size.height * 0.5, loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) { if (loadingProgress == null) { return child; // The image has finished loading } return Center( child: CircularProgressIndicator(), ); }, errorBuilder: (BuildContext context, Object error, StackTrace? stackTrace) { return Center( child: Icon(Icons.error, size: MediaQuery.of(context).size.width * 0.1), ); }, )), Padding( padding: EdgeInsets.symmetric(horizontal: 15), child: ElevatedButton.icon( onPressed: popCamera, icon: Icon(Icons.edit, size: 16), // Edit icon label: Text("Edit Image"), // Button text style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, // Button color foregroundColor: Colors.white, // Text color padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5), ), ), ), Padding( //padding: const EdgeInsets.only(left:15.0,right: 15.0,top:0,bottom: 0), padding: EdgeInsets.symmetric(horizontal: 15), child: TextFormField( controller: inputName, validator: (value) => _validateField(value), decoration: InputDecoration( border: OutlineInputBorder(), labelText: 'Nom', hintText: 'Modifier le nom de l\'évènement'), ), ), _buildGeographicalZoneSearchField(), Padding( padding: const EdgeInsets.only( left: 15.0, right: 15.0, top: 15, bottom: 0), //padding: EdgeInsets.symmetric(horizontal: 15), child: TextFormField( controller: startDatepicker, readOnly: true, validator: (value) => _validateField(value), decoration: InputDecoration( border: OutlineInputBorder(), labelText: 'Date de debut', hintText: 'Cliquez ici pour selectionner une date'), onTap: () => onTapFunctionDatePicker( context: context, position: "start")), ), Padding( padding: const EdgeInsets.only( left: 15.0, right: 15.0, top: 15, bottom: 0), //padding: EdgeInsets.symmetric(horizontal: 15), child: TextFormField( controller: startTimepicker, readOnly: true, validator: (value) => _validateField(value), decoration: InputDecoration( border: OutlineInputBorder(), labelText: 'Heure de debut', hintText: 'Cliquez ici pour selectionner une heure'), onTap: () => onTapFunctionTimePicker( context: context, position: "start")), ), Padding( padding: const EdgeInsets.only( left: 15.0, right: 15.0, top: 15, bottom: 0), //padding: EdgeInsets.symmetric(horizontal: 15), child: TextFormField( controller: endDatepicker, readOnly: true, validator: (value) => _validateField(value), decoration: InputDecoration( border: OutlineInputBorder(), labelText: 'Date de fin', hintText: 'Cliquez ici pour selectionner une date'), onTap: () => onTapFunctionDatePicker( context: context, position: "end")), ), Padding( padding: const EdgeInsets.only( left: 15.0, right: 15.0, top: 15, bottom: 0), //padding: EdgeInsets.symmetric(horizontal: 15), child: TextFormField( controller: endTimepicker, readOnly: true, validator: (value) => _validateField(value), decoration: InputDecoration( border: OutlineInputBorder(), labelText: 'Heure de fin', hintText: 'Cliquez ici pour selectionner une heure'), onTap: () => onTapFunctionTimePicker( context: context, position: "end")), ), TextFieldTags( textfieldTagsController: _stringTagController, initialTags: 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.only( left: 15.0, right: 15.0, top: 15, bottom: 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.only( top: 8, bottom: 8, left: 8, ), 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, ), ), ); }), TextFieldTags( textfieldTagsController: _stringOrgaController, initialTags: initialOrga, textSeparators: const [','], validator: (String tag) { if (_stringOrgaController.getTags!.contains(tag)) { return 'Cet organisateur est déjà rentré'; } return null; }, inputFieldBuilder: (context, inputFieldValues) { return Padding( padding: const EdgeInsets.only( left: 15.0, right: 15.0, top: 15, bottom: 0), child: TextField( controller: inputFieldValues.textEditingController, focusNode: inputFieldValues.focusNode, onChanged: inputFieldValues.onTagChanged, onSubmitted: inputFieldValues.onTagSubmitted, decoration: InputDecoration( border: OutlineInputBorder(), labelText: 'Organisateurs', hintText: inputFieldValues.tags.isNotEmpty ? '' : "Enter un organisateur...", errorText: inputFieldValues.error, prefixIcon: inputFieldValues.tags.isNotEmpty ? SingleChildScrollView( controller: inputFieldValues.tagScrollController, scrollDirection: Axis.vertical, child: Padding( padding: const EdgeInsets.only( top: 8, bottom: 8, left: 8, ), 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( padding: const EdgeInsets.only( left: 15.0, right: 15.0, top: 15, bottom: 0), //padding: EdgeInsets.symmetric(horizontal: 15), child: TextField( controller: inputDesc, keyboardType: TextInputType.multiline, maxLines: 10, decoration: InputDecoration( border: OutlineInputBorder(), labelText: 'Description', hintText: 'Décrire l\'evènement'), ), ), SizedBox( height: 30, ), Container( height: 50, width: 250, decoration: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(20)), child: TextButton( onPressed: () { if (_formKey.currentState!.validate()) { _updateEvent(context); } }, child: Text( 'Ajouter', style: TextStyle(color: Colors.white, fontSize: 25), ), ), ) ], ), ), )); } }