diff --git a/covas_mobile/lib/pages/DisplayPictureScreen.dart b/covas_mobile/lib/pages/DisplayPictureScreen.dart index 9815034..0c6d18a 100644 --- a/covas_mobile/lib/pages/DisplayPictureScreen.dart +++ b/covas_mobile/lib/pages/DisplayPictureScreen.dart @@ -132,7 +132,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, 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 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", images: [file.readAsBytesSync()], modelName: "models/gemini-1.5-pro-latest") .then((value) => searchEvents( diff --git a/covas_mobile/lib/pages/ItemMenu.dart b/covas_mobile/lib/pages/ItemMenu.dart index b45ee48..d14cea6 100644 --- a/covas_mobile/lib/pages/ItemMenu.dart +++ b/covas_mobile/lib/pages/ItemMenu.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'dart:convert'; import 'package:covas_mobile/classes/alert.dart'; +import 'package:covas_mobile/pages/ListItemByTags.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; @@ -15,6 +16,8 @@ import '../variable/globals.dart' as globals; import '../classes/events.dart'; import 'ListItemMenu.dart'; +import 'ListItemByTags.dart'; +import 'ListItemByOrganizers.dart'; void main() { initializeDateFormatting("fr_FR", null).then((_) => (const MyApp())); @@ -68,9 +71,10 @@ class _ItemMenuState extends State with ShowErrorDialog { String eventName = ""; String eventStartDate = ""; String eventDescription = ""; - String organizers = ""; String place = ""; String imgUrl = ""; + List tags = []; + List organizers = []; Events? events; @override @@ -85,10 +89,11 @@ class _ItemMenuState extends State with ShowErrorDialog { var accessToken = prefs.getString("access_token") ?? ""; String formerName = ""; String formerDate = ""; - String formerOrga = ""; String formerMap = ""; String formerImage = ""; String formerDesc = ""; + List formerTags = []; + List formerOrga = []; if (accessToken.isNotEmpty) { var urlGet = Uri.parse("${globals.api}/events/${widget.title}"); @@ -103,6 +108,8 @@ class _ItemMenuState extends State with ShowErrorDialog { formerMap = "${events["place"]} - ${events["zip_code"]} ${events["city"]} - ${events["country"]}"; formerDesc = events["description"]; + formerTags = List.from(events['tags'] as List); + formerOrga = List.from(events['organizers'] as List); final startDate = DateTime.parse(events["start_date"]); final date = DateFormat.yMd().format(startDate); final time = DateFormat.Hm().format(startDate); @@ -116,14 +123,6 @@ class _ItemMenuState extends State with ShowErrorDialog { } formerDate = "${date} ${time} à ${dateE} ${timeE}"; - if (events["organizers"].length > 1) { - formerOrga = "${events['organizers'][0]}"; - for (var i = 1; i < events["organizers"].length; i++) { - formerOrga = "${formerOrga}, ${events['organizers'][i]}"; - } - } else { - formerOrga = "${events['organizers'][0]}"; - } } else { var text = ""; switch (responseGet.statusCode) { @@ -181,6 +180,7 @@ class _ItemMenuState extends State with ShowErrorDialog { place = formerMap; imgUrl = formerImage; eventDescription = formerDesc; + tags = formerTags; }); } @@ -198,7 +198,7 @@ class _ItemMenuState extends State with ShowErrorDialog { appBar: AppBar( // Here we take the value from the MyHomePage object that was created by // the App.build method, and use it to set our appbar title. - title: Text("${eventName}"), + title: Text("${eventName}", overflow: TextOverflow.ellipsis), backgroundColor: Colors.blue, foregroundColor: Colors.white, leading: IconButton( @@ -254,12 +254,50 @@ class _ItemMenuState extends State with ShowErrorDialog { ]), Row(children: [ Flexible( - child: Text( - "${organizers}", - style: TextStyle(fontSize: 15.0), - maxLines: 3, - overflow: TextOverflow.ellipsis, - )) + flex: 3, + fit: FlexFit.tight, + child: Padding( + padding: const EdgeInsets.only( + top: 8, + bottom: 8, + left: 8, + ), + child: Wrap( + runSpacing: 2.0, + spacing: 2.0, + children: organizers.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( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ListItemOrganizers( + organizer: '$tag'))); + }, + child: Text( + '$tag', + style: const TextStyle(color: Colors.white), + ), + ), + ], + ), + ); + }).toList()), + )), ]), Row(children: [ Icon(Icons.description), @@ -272,7 +310,63 @@ class _ItemMenuState extends State with ShowErrorDialog { style: TextStyle(fontSize: 15.0), maxLines: 3, overflow: TextOverflow.ellipsis)) - ]) + ]), + Row(children: [ + Icon(Icons.category), + Text("Tags : ", + style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold)) + ]), + Row( + children: [ + Flexible( + flex: 3, + fit: FlexFit.tight, + child: Padding( + padding: const EdgeInsets.only( + top: 8, + bottom: 8, + left: 8, + ), + child: Wrap( + runSpacing: 2.0, + spacing: 2.0, + children: 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( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => + ListItemTags(tags: '$tag'))); + }, + child: Text( + '$tag', + style: + const TextStyle(color: Colors.white), + ), + ), + ], + ), + ); + }).toList()), + )), + ], + ) ], ))); } diff --git a/covas_mobile/lib/pages/ListItemByOrganizers.dart b/covas_mobile/lib/pages/ListItemByOrganizers.dart new file mode 100644 index 0000000..1500a1b --- /dev/null +++ b/covas_mobile/lib/pages/ListItemByOrganizers.dart @@ -0,0 +1,124 @@ +import 'dart:convert'; +import 'dart:io'; +import "ItemMenu.dart"; + +import 'package:http/http.dart' as http; +import 'package:flutter/material.dart'; +import '../classes/events.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:intl/intl.dart'; +import 'package:intl/date_symbol_data_local.dart'; + +import '../variable/globals.dart' as globals; + +// app starting point +void main() { + initializeDateFormatting("fr_FR", null).then((_) => (const MyApp())); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: const ListItemOrganizers(organizer: "default"), + debugShowCheckedModeBanner: false, + ); + } +} + +// homepage class +class ListItemOrganizers extends StatefulWidget { + const ListItemOrganizers({Key? key, required this.organizer}) + : super(key: key); + + final String organizer; + @override + State createState() => _MyHomePageState(); +} + +// homepage state +class _MyHomePageState extends State { + // variable to call and store future list of posts + + // function to fetch data from api and return future list of posts + static Future> getPosts(organizer) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + var accessToken = prefs.getString("access_token") ?? ""; + final List body = []; + if (accessToken.isNotEmpty) { + var url = Uri.parse("${globals.api}/events?organizers=${organizer}"); + final response = await http.get(url, headers: { + "Content-Type": "application/json", + HttpHeaders.cookieHeader: "access_token=${accessToken}" + }); + final List body = json.decode(utf8.decode(response.bodyBytes)); + return body.map((e) => Events.fromJson(e)).toList(); + } + return body; + } + + // build function + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + // FutureBuilder + child: FutureBuilder>( + future: getPosts(widget.organizer), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + // until data is fetched, show loader + return const CircularProgressIndicator(); + } else if (snapshot.hasData) { + // once data is fetched, display it on screen (call buildPosts()) + final posts = snapshot.data!; + return buildPosts(posts); + } else { + // if no data, show simple Text + return const Text("No data available"); + } + }, + ), + ), + ); + } + + // function to display fetched data on screen + Widget buildPosts(List posts) { + // ListView Builder to show data in a list + return Scaffold( + appBar: AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text("Organisateur : ${widget.organizer}", + overflow: TextOverflow.ellipsis), + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + ), + body: ListView.separated( + itemCount: posts.length, + itemBuilder: (context, index) { + final post = posts[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) { + return Divider(); + }, + ), + ); + } +} diff --git a/covas_mobile/lib/pages/ListItemByTags.dart b/covas_mobile/lib/pages/ListItemByTags.dart new file mode 100644 index 0000000..59ddcbc --- /dev/null +++ b/covas_mobile/lib/pages/ListItemByTags.dart @@ -0,0 +1,122 @@ +import 'dart:convert'; +import 'dart:io'; +import "ItemMenu.dart"; + +import 'package:http/http.dart' as http; +import 'package:flutter/material.dart'; +import '../classes/events.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:intl/intl.dart'; +import 'package:intl/date_symbol_data_local.dart'; + +import '../variable/globals.dart' as globals; + +// app starting point +void main() { + initializeDateFormatting("fr_FR", null).then((_) => (const MyApp())); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: const ListItemTags(tags: "default"), + debugShowCheckedModeBanner: false, + ); + } +} + +// homepage class +class ListItemTags extends StatefulWidget { + const ListItemTags({Key? key, required this.tags}) : super(key: key); + + final String tags; + @override + State createState() => _MyHomePageState(); +} + +// homepage state +class _MyHomePageState extends State { + // variable to call and store future list of posts + + // function to fetch data from api and return future list of posts + static Future> getPosts(tags) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + var accessToken = prefs.getString("access_token") ?? ""; + final List body = []; + if (accessToken.isNotEmpty) { + var url = Uri.parse("${globals.api}/events?tags=${tags}"); + final response = await http.get(url, headers: { + "Content-Type": "application/json", + HttpHeaders.cookieHeader: "access_token=${accessToken}" + }); + final List body = json.decode(utf8.decode(response.bodyBytes)); + return body.map((e) => Events.fromJson(e)).toList(); + } + return body; + } + + // build function + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + // FutureBuilder + child: FutureBuilder>( + future: getPosts(widget.tags), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + // until data is fetched, show loader + return const CircularProgressIndicator(); + } else if (snapshot.hasData) { + // once data is fetched, display it on screen (call buildPosts()) + final posts = snapshot.data!; + return buildPosts(posts); + } else { + // if no data, show simple Text + return const Text("No data available"); + } + }, + ), + ), + ); + } + + // function to display fetched data on screen + Widget buildPosts(List posts) { + // ListView Builder to show data in a list + return Scaffold( + appBar: AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text("Tags : ${widget.tags}", overflow: TextOverflow.ellipsis), + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + ), + body: ListView.separated( + itemCount: posts.length, + itemBuilder: (context, index) { + final post = posts[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) { + return Divider(); + }, + ), + ); + } +} diff --git a/covas_mobile/lib/pages/UpdateEventImage.dart b/covas_mobile/lib/pages/UpdateEventImage.dart index 3ee5d9c..650aa4c 100644 --- a/covas_mobile/lib/pages/UpdateEventImage.dart +++ b/covas_mobile/lib/pages/UpdateEventImage.dart @@ -3,6 +3,7 @@ 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'; @@ -56,13 +57,22 @@ class _UpdateeventImageState extends State TextEditingController startTimepicker = TextEditingController(); TextEditingController endDatepicker = TextEditingController(); TextEditingController endTimepicker = TextEditingController(); + final _stringTagController = StringTagController(); + List initialTags = []; + + final _stringOrgaController = StringTagController(); + List initialOrga = []; onTapFunctionDatePicker( {required BuildContext context, required String position}) async { + String date = "start_date"; + if (position == "end") { + date = "end_date"; + } DateTime? pickedDate = await showDatePicker( context: context, - firstDate: DateTime.parse(widget.events["date"]), - initialDate: DateTime.parse(widget.events["date"]), + firstDate: DateTime.parse(widget.events[date]), + initialDate: DateTime.parse(widget.events[date]), lastDate: DateTime(2104)); if (pickedDate == null) return; if (position == "start") { @@ -75,10 +85,14 @@ class _UpdateeventImageState extends State onTapFunctionTimePicker( {required BuildContext context, required String position}) async { + String date = "start_date"; + if (position == "end") { + date = "end_date"; + } TimeOfDay? pickedDate = await showTimePicker( context: context, initialTime: - TimeOfDay.fromDateTime(DateTime.parse(widget.events["date"]))); + TimeOfDay.fromDateTime(DateTime.parse(widget.events[date]))); if (pickedDate == null) return; if (position == "start") { startTimepicker.text = pickedDate.format(context); @@ -95,6 +109,13 @@ class _UpdateeventImageState extends State return check; } + convertNulltoArray(List check) { + if (check == null) { + return []; + } + return check; + } + String formatDate(String date) { var splitedDate = date.split("-"); var day = splitedDate[0]; @@ -112,6 +133,9 @@ class _UpdateeventImageState extends State var country = inputCountry.text; var zipCode = inputZipCode.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); var endDateFormat = formatDate(endDatepicker.text); @@ -122,7 +146,7 @@ class _UpdateeventImageState extends State print("end date : ${endDate}"); SharedPreferences prefs = await SharedPreferences.getInstance(); var accessToken = prefs.getString("access_token") ?? ""; - List send = ["toto"]; + if (accessToken.isNotEmpty) { try { await dotenv.load(); @@ -167,11 +191,12 @@ class _UpdateeventImageState extends State 'zip_code': zipCode, 'country': country, 'city': city, - 'organizers': send, + 'organizers': organizers, 'latitude': '0.0', 'longitude': '0.0', 'description': description, - "imgUrl": imgUrl + "imgUrl": imgUrl, + "tags": tags })); print(responsePut.statusCode); if ((responsePut.statusCode == 200) || @@ -246,6 +271,8 @@ class _UpdateeventImageState extends State endDatepicker.text = DateFormat("dd-MM-yyyy").format(pickedEndDate); startTimepicker.text = DateFormat("HH-mm").format(pickedStartDate); endTimepicker.text = DateFormat("HH-mm").format(pickedEndDate); + initialTags = List.from(widget.events['tags'] as List); + initialOrga = List.from(widget.events['organizers'] as List); } @override @@ -254,179 +281,388 @@ class _UpdateeventImageState extends State 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: SingleChildScrollView( - child: Column( - children: [ - 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.imagePath))), - ), - ), - Padding( - //padding: const EdgeInsets.only(left:15.0,right: 15.0,top:0,bottom: 0), - padding: EdgeInsets.symmetric(horizontal: 15), - child: TextField( - controller: inputName, - decoration: InputDecoration( - border: OutlineInputBorder(), - labelText: 'Nom', - 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: TextField( - controller: inputAddress, - 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: TextField( - controller: inputZipCode, - 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: TextField( - controller: inputCity, - 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: TextField( - controller: inputCountry, - decoration: InputDecoration( - border: OutlineInputBorder(), - labelText: 'Pays', - hintText: 'Entrer un pays'), - ), - ), - Padding( - padding: const EdgeInsets.only( - left: 15.0, right: 15.0, top: 15, bottom: 0), - //padding: EdgeInsets.symmetric(horizontal: 15), - child: TextField( - controller: startDatepicker, - readOnly: true, - decoration: InputDecoration( - border: OutlineInputBorder(), - labelText: 'Date de debut de l\'évènement', - 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: TextField( - controller: startTimepicker, - readOnly: true, - decoration: InputDecoration( - border: OutlineInputBorder(), - labelText: 'Heure de debut de l\'évènement', - 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: TextField( - controller: endDatepicker, - readOnly: true, - decoration: InputDecoration( - border: OutlineInputBorder(), - labelText: 'Date de fin de l\'évènement', - 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: TextField( - controller: endTimepicker, - readOnly: true, - decoration: InputDecoration( - border: OutlineInputBorder(), - labelText: 'Heure de fin de l\'évènement', - hintText: 'Cliquez ici pour selectionner une heure'), - onTap: () => onTapFunctionTimePicker( - 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: 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: () { - _updateEvent(context); - }, - child: Text( - 'Ajouter', - style: TextStyle(color: Colors.white, fontSize: 25), - ), - ), - ) - ], + 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: [ + 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.imagePath))), + ), + ), + 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'), + ), + ), + 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'), + ), + ), + 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), + ), + ), + ) + ], + ), + ), + )); } } diff --git a/covas_mobile/pubspec.lock b/covas_mobile/pubspec.lock index e3c01b0..84f9170 100644 --- a/covas_mobile/pubspec.lock +++ b/covas_mobile/pubspec.lock @@ -581,6 +581,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.2" + textfield_tags: + dependency: "direct main" + description: + name: textfield_tags + sha256: d1f2204114157a1296bb97c20d7f8c8c7fd036212812afb2e19de7bb34acc55b + url: "https://pub.dev" + source: hosted + version: "3.0.1" typed_data: dependency: transitive description: diff --git a/covas_mobile/pubspec.yaml b/covas_mobile/pubspec.yaml index 59db286..418204f 100644 --- a/covas_mobile/pubspec.yaml +++ b/covas_mobile/pubspec.yaml @@ -45,6 +45,7 @@ dependencies: flutter_dotenv: ^5.1.0 image_picker: ^1.1.2 date_format_field: ^0.1.0 + textfield_tags: ^3.0.1 dev_dependencies: flutter_test: