diff --git a/covas_mobile/lib/pages/EditProfile.dart b/covas_mobile/lib/pages/EditProfile.dart new file mode 100644 index 0000000..593d1fe --- /dev/null +++ b/covas_mobile/lib/pages/EditProfile.dart @@ -0,0 +1,701 @@ +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 '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: EditProfile(), + ); + } +} + +class EditProfile extends StatefulWidget { + const EditProfile({super.key}); + + @override + _EditProfileState createState() => _EditProfileState(); +} + +class _EditProfileState 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() { + super.initState(); + } + + final _formKey = GlobalKey(); + String? _validateField(String? value) { + return value!.isEmpty ? 'Champ requis' : null; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + 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), + ), + ), + ) + ], + ), + ), + )); + } +}