diff --git a/lib/Components/geometry_input_container.dart b/lib/Components/geometry_input_container.dart new file mode 100644 index 0000000..ad774a1 --- /dev/null +++ b/lib/Components/geometry_input_container.dart @@ -0,0 +1,144 @@ +import 'package:flutter/material.dart'; +import 'package:manager_api_new/api.dart'; +import 'package:manager_app/Components/map_geometry_picker.dart'; +import 'package:manager_app/constants.dart'; + +class GeometryInputContainer extends StatelessWidget { + final String label; + final GeometryDTO? initialGeometry; + final String? initialColor; + final Function(GeometryDTO, String) onSave; + final Color color; + final bool isSmall; + + GeometryInputContainer({ + required this.label, + this.initialGeometry, + this.initialColor, + required this.onSave, + this.color = kPrimaryColor, + this.isSmall = false, + }); + + @override + Widget build(BuildContext context) { + String typeInfo = initialGeometry?.type ?? "Aucun"; + int pointCount = 0; + if (initialGeometry?.coordinates != null) { + if (initialGeometry!.type == "Point") { + pointCount = 1; + } else if (initialGeometry!.coordinates is List) { + pointCount = (initialGeometry!.coordinates as List).length; + } + } + + Size size = MediaQuery.of(context).size; + + return Container( + margin: EdgeInsets.symmetric(vertical: 4), + child: Row( + children: [ + Container( + width: 150, + child: Text( + label, + style: const TextStyle( + fontWeight: FontWeight.w400, + fontSize: 16, + ), + ), + ), + Container( + width: isSmall ? size.width * 0.15 : size.width * 0.25, + child: InkWell( + onTap: () { + showDialog( + context: context, + builder: (context) => MapGeometryPicker( + initialGeometry: initialGeometry, + initialColor: initialColor, + onSave: onSave, + ), + ); + }, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(29), + border: Border.all(color: kSecond), + ), + child: Row( + children: [ + Icon( + _getIconForType(initialGeometry?.type), + color: color, + size: 20, + ), + SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + initialGeometry != null + ? "$typeInfo ($pointCount pts)" + : "Définir", + style: TextStyle( + fontSize: 13, + color: initialGeometry != null ? kBlack : kSecond, + overflow: TextOverflow.ellipsis, + ), + ), + if (initialColor != null) + Row( + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: _parseColor(initialColor!), + shape: BoxShape.circle, + ), + ), + SizedBox(width: 4), + Text(initialColor!, + style: TextStyle( + fontSize: 9, color: Colors.grey)), + ], + ), + ], + ), + ), + Icon(Icons.edit, color: kSecond, size: 16), + ], + ), + ), + ), + ), + ], + ), + ); + } + + IconData _getIconForType(String? type) { + switch (type) { + case "Point": + return Icons.location_on; + case "LineString": + return Icons.show_chart; + case "Polygon": + return Icons.pentagon; + default: + return Icons.map; + } + } + + Color _parseColor(String hex) { + try { + return Color(int.parse(hex.replaceFirst('#', '0xFF'))); + } catch (e) { + return kPrimaryColor; + } + } +} diff --git a/lib/Components/map_geometry_picker.dart b/lib/Components/map_geometry_picker.dart new file mode 100644 index 0000000..6d681cf --- /dev/null +++ b/lib/Components/map_geometry_picker.dart @@ -0,0 +1,361 @@ +import 'dart:math' as math; +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:latlong2/latlong.dart'; +import 'package:manager_api_new/api.dart'; +import 'package:manager_app/constants.dart'; +import 'package:manager_app/Components/rounded_button.dart'; +import 'package:manager_app/Components/color_picker.dart'; + +class MapGeometryPicker extends StatefulWidget { + final GeometryDTO? initialGeometry; + final String? initialColor; + final Function(GeometryDTO, String) onSave; + + MapGeometryPicker({ + this.initialGeometry, + this.initialColor, + required this.onSave, + }); + + @override + _MapGeometryPickerState createState() => _MapGeometryPickerState(); +} + +class _MapGeometryPickerState extends State { + List points = []; + String currentType = "Point"; + Color selectedColor = kPrimaryColor; + final MapController _mapController = MapController(); + + @override + void initState() { + super.initState(); + if (widget.initialGeometry != null) { + currentType = widget.initialGeometry!.type ?? "Point"; + _parseInitialGeometry(); + } + if (widget.initialColor != null) { + try { + selectedColor = _hexToColor(widget.initialColor!); + } catch (e) { + selectedColor = kPrimaryColor; + } + } + } + + Color _hexToColor(String hex) { + hex = hex.replaceFirst('#', ''); + if (hex.length == 6) hex = 'FF' + hex; + return Color(int.parse(hex, radix: 16)); + } + + void _parseInitialGeometry() { + if (widget.initialGeometry?.coordinates == null) return; + + try { + if (currentType == "Point") { + var coords = widget.initialGeometry!.coordinates as List; + points = [LatLng(coords[0].toDouble(), coords[1].toDouble())]; + } else if (currentType == "LineString" || currentType == "Polygon") { + var list = widget.initialGeometry!.coordinates as List; + points = list.map((e) { + var pair = e as List; + return LatLng(pair[0].toDouble(), pair[1].toDouble()); + }).toList(); + } + } catch (e) { + print("Error parsing geometry: $e"); + } + } + + void _handleTap(TapPosition tapPosition, LatLng latLng) { + setState(() { + if (currentType == "Point") { + points = [latLng]; + } else { + points.add(latLng); + } + }); + } + + GeometryDTO _buildGeometry() { + if (currentType == "Point") { + return GeometryDTO( + type: "Point", + coordinates: points.isNotEmpty + ? [points[0].latitude, points[0].longitude] + : null, + ); + } else { + return GeometryDTO( + type: currentType, + coordinates: points.map((e) => [e.latitude, e.longitude]).toList(), + ); + } + } + + String _colorToHex(Color color) { + return '#${color.value.toRadixString(16).padLeft(8, '0').substring(2)}'; + } + + @override + Widget build(BuildContext context) { + Size size = MediaQuery.of(context).size; + return Dialog( + insetPadding: EdgeInsets.all(20), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), + child: Container( + width: size.width * 0.9, + height: size.height * 0.9, + child: ClipRRect( + borderRadius: BorderRadius.circular(15), + child: Column( + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), + color: kPrimaryColor, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Éditeur de Géométrie", + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold)), + Row( + children: [ + IconButton( + icon: Icon(Icons.palette, color: Colors.white), + onPressed: () { + showColorPicker(selectedColor, (Color color) { + setState(() => selectedColor = color); + }, context); + }, + ), + IconButton( + icon: Icon(Icons.delete, color: Colors.white), + onPressed: () => setState(() => points.clear()), + ), + IconButton( + icon: Icon(Icons.close, color: Colors.white), + onPressed: () => Navigator.pop(context), + ), + ], + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + children: [ + Wrap( + spacing: 12, + alignment: WrapAlignment.center, + children: [ + _buildTypeButton("Point", Icons.location_on), + _buildTypeButton("LineString", Icons.show_chart), + _buildTypeButton("Polygon", Icons.pentagon), + ], + ), + SizedBox(height: 8), + Text( + _getInstructions(), + style: TextStyle( + fontStyle: FontStyle.italic, color: Colors.grey[600]), + textAlign: TextAlign.center, + ), + ], + ), + ), + Expanded( + child: Stack( + children: [ + FlutterMap( + key: _mapKey, + mapController: _mapController, + options: MapOptions( + initialCenter: points.isNotEmpty + ? points[0] + : LatLng(50.429333, 4.891434), + initialZoom: 14, + onTap: _handleTap, + ), + children: [ + TileLayer( + urlTemplate: + "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + ), + if (currentType == "Polygon" && points.length >= 3) + PolygonLayer( + polygons: [ + Polygon( + points: points, + color: selectedColor.withOpacity(0.3), + borderStrokeWidth: 2, + borderColor: selectedColor, + isFilled: true, + ), + ], + ), + if (currentType == "LineString" && points.length >= 2) + PolylineLayer( + polylines: [ + Polyline( + points: points, + color: selectedColor, + strokeWidth: 4, + ), + ], + ), + MarkerLayer( + markers: points.asMap().entries.map((entry) { + int idx = entry.key; + LatLng p = entry.value; + return Marker( + point: p, + width: 30, + height: 30, + child: GestureDetector( + onPanUpdate: (details) { + _handleDrag(idx, details); + }, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + border: Border.all( + color: selectedColor, width: 2), + boxShadow: [ + BoxShadow( + blurRadius: 4, + color: Colors.black26, + offset: Offset(0, 2)) + ], + ), + child: Center( + child: Icon(Icons.circle, + color: selectedColor, size: 14), + ), + ), + ), + ); + }).toList(), + ), + ], + ), + ], + ), + ), + Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 4, + offset: Offset(0, -2)) + ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + height: 45, + child: RoundedButton( + text: "Annuler", + color: Colors.grey[200]!, + textColor: Colors.black87, + press: () => Navigator.pop(context), + fontSize: 16, + horizontal: 24, + ), + ), + SizedBox(width: 12), + Container( + height: 45, + child: RoundedButton( + text: "Sauvegarder", + color: kPrimaryColor, + press: () { + widget.onSave( + _buildGeometry(), _colorToHex(selectedColor)); + Navigator.pop(context); + }, + fontSize: 16, + horizontal: 32, + ), + ), + ], + ), + ) + ], + ), + ), + ), + ); + } + + String _getInstructions() { + switch (currentType) { + case "Point": + return "Touchez la carte pour placer le point. Faites glisser le point pour le déplacer."; + case "LineString": + return "Touchez la carte pour ajouter des points à la ligne. Faites glisser un point pour le déplacer."; + case "Polygon": + return "Touchez la carte pour définir les sommets du polygone. Faites glisser un sommet pour le déplacer."; + default: + return ""; + } + } + + final GlobalKey _mapKey = GlobalKey(); + + void _handleDrag(int index, DragUpdateDetails details) { + if (index < 0 || index >= points.length) return; + + final RenderBox? mapBox = + _mapKey.currentContext?.findRenderObject() as RenderBox?; + if (mapBox != null) { + final Offset localOffset = mapBox.globalToLocal(details.globalPosition); + try { + LatLng newLatLng = _mapController.camera + .pointToLatLng(math.Point(localOffset.dx, localOffset.dy)); + setState(() { + points[index] = newLatLng; + }); + } catch (e) { + print("Error dragging point: $e"); + } + } + } + + Widget _buildTypeButton(String type, IconData icon) { + bool isSelected = currentType == type; + return ChoiceChip( + label: Text(type == "LineString" + ? "Ligne" + : type == "Polygon" + ? "Polygone" + : "Point"), + avatar: Icon(icon, + size: 18, color: isSelected ? Colors.white : kPrimaryColor), + selected: isSelected, + onSelected: (val) { + if (val) { + setState(() { + currentType = type; + if (currentType == "Point" && points.length > 1) { + points = [points[0]]; + } + }); + } + }, + selectedColor: kPrimaryColor, + labelStyle: TextStyle(color: isSelected ? Colors.white : kPrimaryColor), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + ); + } +} diff --git a/lib/Components/rounded_button.dart b/lib/Components/rounded_button.dart index 08268f6..b9305e8 100644 --- a/lib/Components/rounded_button.dart +++ b/lib/Components/rounded_button.dart @@ -11,37 +11,33 @@ class RoundedButton extends StatelessWidget { final double? vertical; final double? horizontal; - const RoundedButton({ - Key? key, - required this.text, - required this.press, - this.icon, - this.color = kPrimaryColor, - this.textColor = kWhite, - required this.fontSize, - this.vertical, - this.horizontal - }) : super(key: key); + const RoundedButton( + {Key? key, + required this.text, + required this.press, + this.icon, + this.color = kPrimaryColor, + this.textColor = kWhite, + required this.fontSize, + this.vertical, + this.horizontal}) + : super(key: key); @override Widget build(BuildContext context) { - //Size size = MediaQuery.of(context).size; return TextButton( style: ButtonStyle( - padding: MaterialStateProperty.resolveWith((states) => EdgeInsets.symmetric(vertical: this.vertical != null ? this.vertical! : 25, horizontal: this.horizontal != null ? this.horizontal!: (icon == null ? 85 : 30))), - backgroundColor: MaterialStateColor.resolveWith((states) => color), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30.0), - ) - ) - ), - - onPressed: () => { - press() - }, - child: getValue(icon) - ); + padding: MaterialStateProperty.all(EdgeInsets.symmetric( + vertical: vertical ?? 12, + horizontal: horizontal ?? (icon == null ? 20 : 15), + )), + backgroundColor: MaterialStateProperty.all(color), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(30.0), + ))), + onPressed: () => {press()}, + child: getValue(icon)); } getValue(icon) { @@ -51,16 +47,17 @@ class RoundedButton extends StatelessWidget { children: [ Center( child: AutoSizeText( - text, - style: new TextStyle(color: textColor, fontSize: fontSize, fontWeight: FontWeight.w400), - maxLines: 2, - maxFontSize: fontSize, - textAlign: TextAlign.center, - ), - ), - SizedBox( - width: 10 + text, + style: new TextStyle( + color: textColor, + fontSize: fontSize, + fontWeight: FontWeight.w400), + maxLines: 2, + maxFontSize: fontSize, + textAlign: TextAlign.center, + ), ), + SizedBox(width: 10), Icon( icon, color: textColor, @@ -71,10 +68,11 @@ class RoundedButton extends StatelessWidget { } else { return AutoSizeText( text, - style: new TextStyle(color: textColor, fontSize: fontSize, fontWeight: FontWeight.w400), + style: new TextStyle( + color: textColor, fontSize: fontSize, fontWeight: FontWeight.w400), maxLines: 2, textAlign: TextAlign.center, ); } } -} \ No newline at end of file +} diff --git a/lib/Screens/Configurations/Section/SubSection/Agenda/agenda_config.dart b/lib/Screens/Configurations/Section/SubSection/Agenda/agenda_config.dart index 657b4a8..2b50510 100644 --- a/lib/Screens/Configurations/Section/SubSection/Agenda/agenda_config.dart +++ b/lib/Screens/Configurations/Section/SubSection/Agenda/agenda_config.dart @@ -1,19 +1,17 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:manager_api_new/api.dart'; +import 'package:manager_app/constants.dart'; import 'package:manager_app/Components/check_input_container.dart'; import 'package:manager_app/Components/multi_string_input_container.dart'; -import 'package:manager_app/Components/resource_input_container.dart'; -import 'package:manager_api_new/api.dart'; import 'package:manager_app/Components/single_select_container.dart'; -import 'dart:convert'; - -import 'package:manager_app/constants.dart'; +import 'showNewOrUpdateEventAgenda.dart'; class AgendaConfig extends StatefulWidget { final String? color; final String? label; final AgendaDTO initialValue; final ValueChanged onChanged; + const AgendaConfig({ Key? key, this.color, @@ -31,21 +29,20 @@ class _AgendaConfigState extends State { @override void initState() { - AgendaDTO test = widget.initialValue; - agendaDTO = test; super.initState(); + agendaDTO = widget.initialValue; } @override Widget build(BuildContext context) { - Size size = MediaQuery.of(context).size; + //Size size = MediaQuery.of(context).size; var mapProviderIn = ""; - switch(agendaDTO.agendaMapProvider) { - case MapProvider.Google : + switch (agendaDTO.agendaMapProvider) { + case MapProvider.Google: mapProviderIn = "Google"; break; - case MapProvider.MapBox : + case MapProvider.MapBox: mapProviderIn = "MapBox"; break; default: @@ -53,17 +50,16 @@ class _AgendaConfigState extends State { break; } - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Container( - height: size.height * 0.3, + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ CheckInputContainer( label: "En ligne :", - isChecked: agendaDTO.isOnlineAgenda!, + isChecked: agendaDTO.isOnlineAgenda ?? true, onChanged: (value) { setState(() { agendaDTO.isOnlineAgenda = value; @@ -72,12 +68,13 @@ class _AgendaConfigState extends State { }, ), SingleSelectContainer( - label: "Service carte :", - color: Colors.black, - initialValue: mapProviderIn, - inputValues: map_providers, - onChanged: (String value) { - switch(value) { + label: "Service carte :", + color: Colors.black, + initialValue: mapProviderIn, + inputValues: ["Google", "MapBox"], + onChanged: (String value) { + setState(() { + switch (value) { case "Google": agendaDTO.agendaMapProvider = MapProvider.Google; break; @@ -86,40 +83,121 @@ class _AgendaConfigState extends State { break; } widget.onChanged(agendaDTO); - } - ), - MultiStringInputContainer( - label: "Fichiers json :", - resourceTypes: [ResourceType.Json, ResourceType.JsonUrl], - modalLabel: "JSON", - color: kPrimaryColor, - initialValue: agendaDTO.resourceIds!, - isTitle: false, - onGetResult: (value) { - setState(() { - if (agendaDTO.resourceIds != value) { - agendaDTO.resourceIds = value; - //save(true, articleDTO, appContext); - widget.onChanged(agendaDTO); - } }); }, - maxLines: 1, ), + if (agendaDTO.isOnlineAgenda == true) + MultiStringInputContainer( + label: "Fichiers json :", + resourceTypes: [ResourceType.Json, ResourceType.JsonUrl], + modalLabel: "JSON", + color: kPrimaryColor, + initialValue: agendaDTO.resourceIds ?? [], + isTitle: false, + onGetResult: (value) { + setState(() { + agendaDTO.resourceIds = value; + widget.onChanged(agendaDTO); + }); + }, + maxLines: 1, + ), ], ), ), - ), + if (agendaDTO.isOnlineAgenda == false) ...[ + Divider(), + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Évènements Manuels", + style: + TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + ElevatedButton.icon( + icon: Icon(Icons.add), + label: Text("Ajouter un évènement"), + onPressed: () { + showNewOrUpdateEventAgenda( + context, + null, + agendaDTO.id ?? "temp", + (newEvent) { + setState(() { + agendaDTO.events = [ + ...(agendaDTO.events ?? []), + newEvent + ]; + widget.onChanged(agendaDTO); + }); + }, + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: kSuccess, foregroundColor: kWhite), + ), + ], + ), + ), + Expanded( + child: (agendaDTO.events == null || agendaDTO.events!.isEmpty) + ? Center( + child: Text("Aucun évènement manuel", + style: TextStyle(fontStyle: FontStyle.italic))) + : ListView.builder( + itemCount: agendaDTO.events!.length, + itemBuilder: (context, index) { + final event = agendaDTO.events![index]; + return Card( + margin: + EdgeInsets.symmetric(horizontal: 16, vertical: 4), + child: ListTile( + title: Text(event.label + ?.firstWhere((t) => t.language == 'FR', + orElse: () => event.label![0]) + .value ?? + "Évènement $index"), + subtitle: + Text(event.address?.address ?? "Pas d'adresse"), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(Icons.edit, color: kPrimaryColor), + onPressed: () { + showNewOrUpdateEventAgenda( + context, + event, + agendaDTO.id ?? "temp", + (updatedEvent) { + setState(() { + agendaDTO.events![index] = updatedEvent; + widget.onChanged(agendaDTO); + }); + }, + ); + }, + ), + IconButton( + icon: Icon(Icons.delete, color: kError), + onPressed: () { + setState(() { + agendaDTO.events!.removeAt(index); + widget.onChanged(agendaDTO); + }); + }, + ), + ], + ), + ), + ); + }, + ), + ), + ], + ], ); - /*return ResourceInputContainer( - label: "Fichier JSON :", - initialValue: agendaDTO.resourceId == null ? '': agendaDTO.resourceId, - inResourceTypes: [ResourceType.Json, ResourceType.JsonUrl], - onChanged: (ResourceDTO resourceDTO) { - agendaDTO.resourceUrl = resourceDTO.url; - agendaDTO.resourceId = resourceDTO.id; - widget.onChanged(jsonEncode(agendaDTO).toString()); - } - );*/ } } diff --git a/lib/Screens/Configurations/Section/SubSection/Agenda/showNewOrUpdateEventAgenda.dart b/lib/Screens/Configurations/Section/SubSection/Agenda/showNewOrUpdateEventAgenda.dart new file mode 100644 index 0000000..32656f6 --- /dev/null +++ b/lib/Screens/Configurations/Section/SubSection/Agenda/showNewOrUpdateEventAgenda.dart @@ -0,0 +1,208 @@ +import 'package:flutter/material.dart'; +import 'package:manager_api_new/api.dart'; +import 'package:manager_app/constants.dart'; +import 'package:manager_app/Components/rounded_button.dart'; +import 'package:manager_app/Components/multi_string_input_container.dart'; +import 'package:manager_app/Components/string_input_container.dart'; +import 'package:manager_app/Components/geometry_input_container.dart'; + +void showNewOrUpdateEventAgenda( + BuildContext context, + EventAgendaDTO? event, + String agendaId, + Function(EventAgendaDTO) onSave, +) { + EventAgendaDTO workingEvent = event != null + ? EventAgendaDTO.fromJson(event.toJson())! + : EventAgendaDTO( + label: [], + description: [], + sectionAgendaId: agendaId, + address: EventAgendaDTOAddress( + address: "", + city: "", + country: "", + ), + ); + + showDialog( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (context, setState) { + final double screenWidth = MediaQuery.of(context).size.width; + final double screenHeight = MediaQuery.of(context).size.height; + final double dialogWidth = screenWidth * 0.78; + final double contentWidth = dialogWidth - 48; + final double halfWidth = (contentWidth - 20) / 2; + final double thirdWidth = (contentWidth - 40) / 3; + + return Dialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Container( + width: dialogWidth, + constraints: BoxConstraints(maxHeight: screenHeight * 0.88), + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + event == null ? "Nouvel Évènement" : "Modifier l'Évènement", + style: TextStyle( + color: kPrimaryColor, + fontSize: 20, + fontWeight: FontWeight.bold), + ), + SizedBox(height: 16), + Flexible( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Titre + Description côte à côte + Row( + children: [ + SizedBox( + width: halfWidth, + child: MultiStringInputContainer( + label: "Titre :", + modalLabel: "Titre de l'évènement", + initialValue: workingEvent.label ?? [], + onGetResult: (val) => + setState(() => workingEvent.label = val), + maxLines: 1, + isTitle: true, + ), + ), + SizedBox(width: 20), + SizedBox( + width: halfWidth, + child: MultiStringInputContainer( + label: "Description :", + modalLabel: "Description de l'évènement", + initialValue: workingEvent.description ?? [], + onGetResult: (val) => setState( + () => workingEvent.description = val), + maxLines: 5, + isTitle: false, + ), + ), + ], + ), + Divider(height: 24), + // Site | Tel | Email + Row( + children: [ + SizedBox( + width: thirdWidth, + child: StringInputContainer( + label: "Site Web :", + initialValue: workingEvent.website ?? "", + onChanged: (val) => setState( + () => workingEvent.website = val), + ), + ), + SizedBox(width: 20), + SizedBox( + width: thirdWidth, + child: StringInputContainer( + label: "Téléphone :", + initialValue: workingEvent.phone ?? "", + onChanged: (val) => + setState(() => workingEvent.phone = val), + ), + ), + SizedBox(width: 20), + SizedBox( + width: thirdWidth, + child: StringInputContainer( + label: "Email :", + initialValue: workingEvent.email ?? "", + onChanged: (val) => + setState(() => workingEvent.email = val), + ), + ), + ], + ), + Divider(height: 24), + Text("Localisation / Géométrie", + style: TextStyle(fontWeight: FontWeight.bold)), + SizedBox(height: 8), + StringInputContainer( + label: "Adresse (Texte) :", + initialValue: workingEvent.address?.address ?? "", + onChanged: (val) { + setState(() { + if (workingEvent.address == null) + workingEvent.address = + EventAgendaDTOAddress(); + workingEvent.address!.address = val; + }); + }, + ), + SizedBox(height: 8), + GeometryInputContainer( + label: "Emplacement sur la carte :", + initialGeometry: workingEvent.address?.geometry != + null + ? GeometryDTO.fromJson( + workingEvent.address!.geometry!.toJson()) + : null, + initialColor: workingEvent.address?.polyColor, + onSave: (geo, color) { + setState(() { + if (workingEvent.address == null) + workingEvent.address = + EventAgendaDTOAddress(); + workingEvent.address!.geometry = + EventAddressDTOGeometry.fromJson( + geo.toJson()); + workingEvent.address!.polyColor = color; + }); + }, + ), + ], + ), + ), + ), + SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + height: 46, + child: RoundedButton( + text: "Annuler", + press: () => Navigator.pop(context), + color: kSecond, + fontSize: 15, + horizontal: 24, + ), + ), + SizedBox(width: 12), + SizedBox( + height: 46, + child: RoundedButton( + text: "Sauvegarder", + press: () { + onSave(workingEvent); + Navigator.pop(context); + }, + color: kPrimaryColor, + fontSize: 15, + horizontal: 24, + ), + ), + ], + ), + ], + ), + ), + ); + }, + ); + }, + ); +} diff --git a/lib/Screens/Configurations/Section/SubSection/Event/event_config.dart b/lib/Screens/Configurations/Section/SubSection/Event/event_config.dart new file mode 100644 index 0000000..c945d40 --- /dev/null +++ b/lib/Screens/Configurations/Section/SubSection/Event/event_config.dart @@ -0,0 +1,253 @@ +import 'package:flutter/material.dart'; +import 'package:manager_api_new/api.dart'; +import 'package:manager_app/constants.dart'; +import 'package:intl/intl.dart'; +import 'showNewOrUpdateProgrammeBlock.dart'; + +class EventConfig extends StatefulWidget { + final SectionEventDTO initialValue; + final ValueChanged onChanged; + + const EventConfig({ + Key? key, + required this.initialValue, + required this.onChanged, + }) : super(key: key); + + @override + _EventConfigState createState() => _EventConfigState(); +} + +class _EventConfigState extends State { + late SectionEventDTO eventDTO; + + @override + void initState() { + super.initState(); + eventDTO = widget.initialValue; + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + Expanded( + child: ListTile( + title: Text("Date de début"), + subtitle: Text(eventDTO.startDate != null + ? DateFormat('dd/MM/yyyy HH:mm') + .format(eventDTO.startDate!) + : "Non définie"), + trailing: Icon(Icons.calendar_today), + onTap: () async { + DateTime initialDate = eventDTO.startDate ?? DateTime.now(); + if (initialDate.isBefore(DateTime(2000))) { + initialDate = DateTime.now(); + } + DateTime? picked = await showDatePicker( + context: context, + initialDate: initialDate, + firstDate: DateTime(2000), + lastDate: DateTime(2100), + builder: (context, child) { + return Theme( + data: Theme.of(context).copyWith( + colorScheme: ColorScheme.light( + primary: kPrimaryColor, + onPrimary: kWhite, + onSurface: kSecond, + ), + ), + child: child!, + ); + }, + ); + if (picked != null) { + TimeOfDay? time = await showTimePicker( + context: context, + initialTime: TimeOfDay.fromDateTime( + eventDTO.startDate ?? DateTime.now()), + builder: (context, child) { + return Theme( + data: Theme.of(context).copyWith( + colorScheme: ColorScheme.light( + primary: kPrimaryColor, + onPrimary: kWhite, + onSurface: kSecond, + ), + ), + child: child!, + ); + }, + ); + if (time != null) { + setState(() { + eventDTO.startDate = DateTime(picked.year, + picked.month, picked.day, time.hour, time.minute); + widget.onChanged(eventDTO); + }); + } + } + }, + ), + ), + Expanded( + child: ListTile( + title: Text("Date de fin"), + subtitle: Text(eventDTO.endDate != null + ? DateFormat('dd/MM/yyyy HH:mm').format(eventDTO.endDate!) + : "Non définie"), + trailing: Icon(Icons.calendar_today), + onTap: () async { + DateTime initialDate = eventDTO.endDate ?? + DateTime.now().add(Duration(days: 1)); + if (initialDate.isBefore(DateTime(2000))) { + initialDate = DateTime.now().add(Duration(days: 1)); + } + DateTime? picked = await showDatePicker( + context: context, + initialDate: initialDate, + firstDate: DateTime(2000), + lastDate: DateTime(2100), + builder: (context, child) { + return Theme( + data: Theme.of(context).copyWith( + colorScheme: ColorScheme.light( + primary: kPrimaryColor, + onPrimary: kWhite, + onSurface: kSecond, + ), + ), + child: child!, + ); + }, + ); + if (picked != null) { + TimeOfDay? time = await showTimePicker( + context: context, + initialTime: TimeOfDay.fromDateTime(eventDTO.endDate ?? + DateTime.now().add(Duration(days: 1))), + builder: (context, child) { + return Theme( + data: Theme.of(context).copyWith( + colorScheme: ColorScheme.light( + primary: kPrimaryColor, + onPrimary: kWhite, + onSurface: kSecond, + ), + ), + child: child!, + ); + }, + ); + if (time != null) { + setState(() { + eventDTO.endDate = DateTime(picked.year, picked.month, + picked.day, time.hour, time.minute); + widget.onChanged(eventDTO); + }); + } + } + }, + ), + ), + ], + ), + ), + Divider(), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Programme", + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + ElevatedButton.icon( + icon: Icon(Icons.add), + label: Text("Ajouter un bloc"), + onPressed: () { + showNewOrUpdateProgrammeBlock( + context, + null, + (newBlock) { + setState(() { + eventDTO.programme = [ + ...(eventDTO.programme ?? []), + newBlock + ]; + widget.onChanged(eventDTO); + }); + }, + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: kSuccess, foregroundColor: kWhite), + ), + ], + ), + ), + Expanded( + child: (eventDTO.programme == null || eventDTO.programme!.isEmpty) + ? Center( + child: Text("Aucun bloc de programme défini", + style: TextStyle(fontStyle: FontStyle.italic))) + : ListView.builder( + itemCount: eventDTO.programme!.length, + itemBuilder: (context, index) { + final block = eventDTO.programme![index]; + return Card( + margin: EdgeInsets.symmetric(horizontal: 16, vertical: 4), + child: ListTile( + title: Text( + block.title != null && block.title!.isNotEmpty + ? block.title! + .firstWhere((t) => t.language == 'FR', + orElse: () => block.title![0]) + .value ?? + "Bloc ${index + 1}" + : "Bloc ${index + 1}"), + subtitle: Text( + "${block.startTime != null ? DateFormat('HH:mm').format(block.startTime!) : '??'} - ${block.endTime != null ? DateFormat('HH:mm').format(block.endTime!) : '??'}"), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(Icons.edit, color: kPrimaryColor), + onPressed: () { + showNewOrUpdateProgrammeBlock( + context, + block, + (updatedBlock) { + setState(() { + eventDTO.programme![index] = updatedBlock; + widget.onChanged(eventDTO); + }); + }, + ); + }, + ), + IconButton( + icon: Icon(Icons.delete, color: kError), + onPressed: () { + setState(() { + eventDTO.programme!.removeAt(index); + widget.onChanged(eventDTO); + }); + }, + ), + ], + ), + ), + ); + }, + ), + ), + ], + ); + } +} diff --git a/lib/Screens/Configurations/Section/SubSection/Event/showNewOrUpdateProgrammeBlock.dart b/lib/Screens/Configurations/Section/SubSection/Event/showNewOrUpdateProgrammeBlock.dart new file mode 100644 index 0000000..af11f66 --- /dev/null +++ b/lib/Screens/Configurations/Section/SubSection/Event/showNewOrUpdateProgrammeBlock.dart @@ -0,0 +1,235 @@ +import 'package:flutter/material.dart'; +import 'package:manager_api_new/api.dart'; +import 'package:manager_app/constants.dart'; +import 'package:manager_app/Components/rounded_button.dart'; +import 'package:manager_app/Components/multi_string_input_container.dart'; +import 'package:intl/intl.dart'; + +void showNewOrUpdateProgrammeBlock( + BuildContext context, + ProgrammeBlock? block, + Function(ProgrammeBlock) onSave, +) { + ProgrammeBlock workingBlock = block != null + ? ProgrammeBlock.fromJson(block.toJson())! + : ProgrammeBlock( + title: [], + description: [], + startTime: DateTime.now(), + endTime: DateTime.now().add(Duration(hours: 1)), + ); + + showDialog( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (context, setState) { + final double screenWidth = MediaQuery.of(context).size.width; + final double screenHeight = MediaQuery.of(context).size.height; + final double dialogWidth = screenWidth * 0.7; + final double contentWidth = dialogWidth - 48; + final double halfWidth = (contentWidth - 20) / 2; + + return Dialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Container( + width: dialogWidth, + constraints: BoxConstraints(maxHeight: screenHeight * 0.85), + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + block == null + ? "Nouveau Bloc de Programme" + : "Modifier le Bloc", + style: TextStyle( + color: kPrimaryColor, + fontSize: 20, + fontWeight: FontWeight.bold), + ), + SizedBox(height: 16), + Flexible( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Titre + Description côte à côte + Row( + children: [ + SizedBox( + width: halfWidth, + child: MultiStringInputContainer( + label: "Titre :", + modalLabel: "Titre du bloc", + initialValue: workingBlock.title ?? [], + onGetResult: (val) => + setState(() => workingBlock.title = val), + maxLines: 1, + isTitle: true, + ), + ), + SizedBox(width: 20), + SizedBox( + width: halfWidth, + child: MultiStringInputContainer( + label: "Description :", + modalLabel: "Description du bloc", + initialValue: workingBlock.description ?? [], + onGetResult: (val) => setState( + () => workingBlock.description = val), + maxLines: 1, + isTitle: false, + ), + ), + ], + ), + Divider(height: 24), + // Heures côte à côte + Row( + children: [ + SizedBox( + width: halfWidth, + child: ListTile( + leading: Icon(Icons.schedule, + color: kPrimaryColor), + title: Text("Heure de début"), + subtitle: Text( + workingBlock.startTime != null + ? DateFormat('HH:mm') + .format(workingBlock.startTime!) + : "Non définie", + style: TextStyle( + color: kPrimaryColor, + fontWeight: FontWeight.bold), + ), + onTap: () async { + TimeOfDay? time = await showTimePicker( + context: context, + initialTime: TimeOfDay.fromDateTime( + workingBlock.startTime ?? + DateTime.now()), + builder: (context, child) { + return Theme( + data: Theme.of(context).copyWith( + colorScheme: ColorScheme.light( + primary: kPrimaryColor, + onPrimary: kWhite, + onSurface: kSecond, + ), + ), + child: child!, + ); + }, + ); + if (time != null) { + setState(() { + DateTime now = DateTime.now(); + workingBlock.startTime = DateTime( + now.year, + now.month, + now.day, + time.hour, + time.minute); + }); + } + }, + ), + ), + SizedBox(width: 20), + SizedBox( + width: halfWidth, + child: ListTile( + leading: Icon(Icons.schedule_outlined, + color: kPrimaryColor), + title: Text("Heure de fin"), + subtitle: Text( + workingBlock.endTime != null + ? DateFormat('HH:mm') + .format(workingBlock.endTime!) + : "Non définie", + style: TextStyle( + color: kPrimaryColor, + fontWeight: FontWeight.bold), + ), + onTap: () async { + TimeOfDay? time = await showTimePicker( + context: context, + initialTime: TimeOfDay.fromDateTime( + workingBlock.endTime ?? + DateTime.now() + .add(Duration(hours: 1))), + builder: (context, child) { + return Theme( + data: Theme.of(context).copyWith( + colorScheme: ColorScheme.light( + primary: kPrimaryColor, + onPrimary: kWhite, + onSurface: kSecond, + ), + ), + child: child!, + ); + }, + ); + if (time != null) { + setState(() { + DateTime now = DateTime.now(); + workingBlock.endTime = DateTime( + now.year, + now.month, + now.day, + time.hour, + time.minute); + }); + } + }, + ), + ), + ], + ), + ], + ), + ), + ), + SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + height: 46, + child: RoundedButton( + text: "Annuler", + press: () => Navigator.pop(context), + color: kSecond, + fontSize: 15, + horizontal: 24, + ), + ), + SizedBox(width: 12), + SizedBox( + height: 46, + child: RoundedButton( + text: "Sauvegarder", + press: () { + onSave(workingBlock); + Navigator.pop(context); + }, + color: kPrimaryColor, + fontSize: 15, + horizontal: 24, + ), + ), + ], + ), + ], + ), + ), + ); + }, + ); + }, + ); +} diff --git a/lib/Screens/Configurations/Section/SubSection/Game/game_config.dart b/lib/Screens/Configurations/Section/SubSection/Game/game_config.dart index af30e09..7f1b51e 100644 --- a/lib/Screens/Configurations/Section/SubSection/Game/game_config.dart +++ b/lib/Screens/Configurations/Section/SubSection/Game/game_config.dart @@ -4,8 +4,7 @@ import 'package:manager_app/Components/multi_string_input_and_resource_container import 'package:manager_app/Components/number_input_container.dart'; import 'package:manager_app/Components/resource_input_container.dart'; import 'package:manager_api_new/api.dart'; -import 'dart:convert'; - +import 'package:manager_app/Screens/Configurations/Section/SubSection/Parcours/parcours_config.dart'; import 'package:manager_app/constants.dart'; class GameConfig extends StatefulWidget { @@ -30,131 +29,184 @@ class _GameConfigState extends State { @override void initState() { - GameDTO test = widget.initialValue; - /*if(test.puzzleImage == null) { - test.puzzleImage = ResourceDTO(); - }*/ - gameDTO = test; - gameDTO.rows = gameDTO.rows == null ? 3 : gameDTO.rows; - gameDTO.cols = gameDTO.cols == null ? 3 : gameDTO.cols; - + gameDTO = widget.initialValue; + gameDTO.rows = gameDTO.rows ?? 3; + gameDTO.cols = gameDTO.cols ?? 3; + gameDTO.gameType = gameDTO.gameType ?? GameTypes.number0; super.initState(); } @override Widget build(BuildContext context) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, children: [ - ResourceInputContainer( - label: "Image du puzzle :", - initialValue: gameDTO.puzzleImageId == null ? '': gameDTO.puzzleImageId, - onChanged: (ResourceDTO resourceDTO) { - setState(() { - if(resourceDTO.id == null) - { - gameDTO.puzzleImageId = null; - gameDTO.puzzleImage = null; - } else { - gameDTO.puzzleImageId = resourceDTO.id; - gameDTO.puzzleImage = resourceDTO; - } - widget.onChanged(gameDTO); - }); - } + Text("Type de Jeu : ", + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)), + DropdownButton( + value: gameDTO.gameType, + items: [ + DropdownMenuItem( + value: GameTypes.number0, child: Text("Puzzle")), + DropdownMenuItem( + value: GameTypes.number1, child: Text("Puzzle Glissant")), + DropdownMenuItem( + value: GameTypes.number2, child: Text("Escape Game")), + ], + onChanged: (val) { + setState(() { + gameDTO.gameType = val; + widget.onChanged(gameDTO); + }); + }, ), - Container( - height: 100, - child: MultiStringInputAndResourceContainer( + ], + ), + ), + if (gameDTO.gameType == GameTypes.number2) ...[ + // Escape Mode: Parcours Config + Expanded( + child: ParcoursConfig( + initialValue: gameDTO.guidedPaths ?? [], + parentId: gameDTO.id!, + isEvent: false, + isEscapeMode: true, + onChanged: (paths) { + setState(() { + gameDTO.guidedPaths = paths; + widget.onChanged(gameDTO); + }); + }, + ), + ), + ] else ...[ + // Regular Puzzle Mode + Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ResourceInputContainer( + label: "Image du puzzle :", + initialValue: gameDTO.puzzleImageId ?? '', + onChanged: (ResourceDTO resourceDTO) { + setState(() { + if (resourceDTO.id == null) { + gameDTO.puzzleImageId = null; + gameDTO.puzzleImage = null; + } else { + gameDTO.puzzleImageId = resourceDTO.id; + gameDTO.puzzleImage = resourceDTO; + } + widget.onChanged(gameDTO); + }); + }, + ), + Container( + height: 100, + child: MultiStringInputAndResourceContainer( label: "Message départ :", modalLabel: "Message départ", fontSize: 20, color: kPrimaryColor, - initialValue: gameDTO.messageDebut != null ? gameDTO.messageDebut! : [], - resourceTypes: [ResourceType.Image, ResourceType.ImageUrl, ResourceType.VideoUrl, ResourceType.Video, ResourceType.Audio], + initialValue: gameDTO.messageDebut ?? [], + resourceTypes: [ + ResourceType.Image, + ResourceType.ImageUrl, + ResourceType.VideoUrl, + ResourceType.Video, + ResourceType.Audio + ], onGetResult: (value) { - if (gameDTO.messageDebut != value) { - setState(() { - gameDTO.messageDebut = value; - widget.onChanged(gameDTO); - }); - } + setState(() { + gameDTO.messageDebut = value; + widget.onChanged(gameDTO); + }); }, maxLines: 1, - isTitle: false - ) - ), - Container( - height: 100, - child: MultiStringInputAndResourceContainer( + isTitle: false, + ), + ), + Container( + height: 100, + child: MultiStringInputAndResourceContainer( label: "Message fin :", modalLabel: "Message fin", fontSize: 20, color: kPrimaryColor, - initialValue: gameDTO.messageFin != null ? gameDTO.messageFin! : [], - resourceTypes: [ResourceType.Image, ResourceType.ImageUrl, ResourceType.VideoUrl, ResourceType.Video, ResourceType.Audio], + initialValue: gameDTO.messageFin ?? [], + resourceTypes: [ + ResourceType.Image, + ResourceType.ImageUrl, + ResourceType.VideoUrl, + ResourceType.Video, + ResourceType.Audio + ], onGetResult: (value) { - if (gameDTO.messageFin != value) { - setState(() { - gameDTO.messageFin = value; - widget.onChanged(gameDTO); - }); - } + setState(() { + gameDTO.messageFin = value; + widget.onChanged(gameDTO); + }); }, maxLines: 1, - isTitle: false - ) + isTitle: false, + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Container( + height: 100, + child: NumberInputContainer( + label: "Nombre de lignes :", + initialValue: gameDTO.rows ?? 3, + isSmall: true, + maxLength: 2, + onChanged: (value) { + try { + gameDTO.rows = int.parse(value); + setState(() { + widget.onChanged(gameDTO); + }); + } catch (e) { + showNotification(Colors.orange, kWhite, + 'Cela doit être un chiffre', context, null); + } + }, + ), + ), + Container( + height: 100, + child: NumberInputContainer( + label: "Nombre de colonnes :", + initialValue: gameDTO.cols ?? 3, + isSmall: true, + maxLength: 2, + onChanged: (value) { + try { + gameDTO.cols = int.parse(value); + setState(() { + widget.onChanged(gameDTO); + }); + } catch (e) { + showNotification(Colors.orange, kWhite, + 'Cela doit être un chiffre', context, null); + } + }, + ), + ), + ], ), ], ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Container( - height: 100, - child: NumberInputContainer( - label: "Nombre de lignes :", - initialValue: gameDTO.rows!, - isSmall: true, - maxLength: 2, - onChanged: (value) { - try { - gameDTO.rows = int.parse(value); - setState(() { - widget.onChanged(gameDTO); - }); - } catch (e) { - showNotification(Colors.orange, kWhite, 'Cela doit être un chiffre', context, null); - } - }, - ), - ), - Container( - height: 100, - child: NumberInputContainer( - label: "Nombre de colonnes :", - initialValue: gameDTO.cols!, - isSmall: true, - maxLength: 2, - onChanged: (value) { - try { - gameDTO.cols = int.parse(value); - setState(() { - widget.onChanged(gameDTO); - }); - } catch (e) { - showNotification(Colors.orange, kWhite, 'Cela doit être un chiffre', context, null); - } - }, - ), - ), - ],) ], - ), + ], ); } } diff --git a/lib/Screens/Configurations/Section/SubSection/Map/map_config.dart b/lib/Screens/Configurations/Section/SubSection/Map/map_config.dart index 746a6dd..becde10 100644 --- a/lib/Screens/Configurations/Section/SubSection/Map/map_config.dart +++ b/lib/Screens/Configurations/Section/SubSection/Map/map_config.dart @@ -6,7 +6,6 @@ import 'package:manager_app/Components/confirmation_dialog.dart'; import 'package:manager_app/Components/geoloc_input_container.dart'; import 'package:manager_app/Components/common_loader.dart'; import 'package:manager_app/Components/message_notification.dart'; -import 'package:manager_app/Components/multi_select_dropdown_language_container.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Map/category_input_container.dart'; import 'package:manager_app/Components/dropDown_input_container.dart'; @@ -17,12 +16,11 @@ import 'package:manager_app/Components/single_select_container.dart'; import 'package:manager_app/Components/slider_input_container.dart'; import 'package:manager_app/Components/string_input_container.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Map/showNewOrUpdateGeoPoint.dart'; -import 'package:html/parser.dart' show parse; import 'package:manager_app/app_context.dart'; import 'package:manager_app/client.dart'; import 'package:manager_app/constants.dart'; import 'package:manager_api_new/api.dart'; -import 'dart:convert'; +import 'package:manager_app/Screens/Configurations/Section/SubSection/Parcours/parcours_config.dart'; import 'package:provider/provider.dart'; @@ -47,12 +45,13 @@ class _MapConfigState extends State { late MapDTO mapDTO; late List pointsToShow = []; //List? selectedCategories = []; - String mapType= "hybrid"; - String mapTypeMapBox= "standard"; + String mapType = "hybrid"; + String mapTypeMapBox = "standard"; String filterSearch = ''; - final ValueNotifier?> selectedCategoriesNotifier = ValueNotifier([]); + final ValueNotifier?> selectedCategoriesNotifier = + ValueNotifier([]); final ValueNotifier searchNotifier = ValueNotifier(""); @override @@ -61,8 +60,8 @@ class _MapConfigState extends State { //mapDTO = MapDTO.fromJson(json.decode(widget.initialValue))!; mapDTO = widget.initialValue; - if(mapDTO.mapType != null) { - switch(mapDTO.mapType!.value) { + if (mapDTO.mapType != null) { + switch (mapDTO.mapType!.value) { case 0: mapType = "none"; break; @@ -81,8 +80,8 @@ class _MapConfigState extends State { } } - if(mapDTO.mapTypeMapbox != null) { - switch(mapDTO.mapTypeMapbox!.value) { + if (mapDTO.mapTypeMapbox != null) { + switch (mapDTO.mapTypeMapbox!.value) { case 0: mapTypeMapBox = "standard"; break; @@ -114,11 +113,11 @@ class _MapConfigState extends State { Size size = MediaQuery.of(context).size; var mapProviderIn = ""; - switch(mapDTO.mapProvider) { - case MapProvider.Google : + switch (mapDTO.mapProvider) { + case MapProvider.Google: mapProviderIn = "Google"; break; - case MapProvider.MapBox : + case MapProvider.MapBox: mapProviderIn = "MapBox"; break; default: @@ -126,322 +125,482 @@ class _MapConfigState extends State { break; } - return - SingleChildScrollView( - child: Column( - children: [ - Container( - height: size.height * 0.25, - width: double.infinity, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, + return DefaultTabController( + length: 2, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TabBar( + labelColor: kPrimaryColor, + unselectedLabelColor: Colors.grey, + indicatorColor: kPrimaryColor, + tabs: [ + Tab(icon: Icon(Icons.map), text: "Points d'Intérêt"), + Tab(icon: Icon(Icons.route), text: "Parcours"), + ], + ), + Container( + height: 500, // Reduced from 550 to 500 to avoid 8.5px overflow + child: TabBarView( + children: [ + // Tab 1: Configuration & Points + SingleChildScrollView( + child: Column( children: [ - SingleSelectContainer( - label: "Service :", - color: Colors.black, - initialValue: mapProviderIn, - inputValues: map_providers, - onChanged: (String value) { - switch(value) { - case "Google": - mapDTO.mapProvider = MapProvider.Google; - break; - case "MapBox": - mapDTO.mapProvider = MapProvider.MapBox; - break; - } - //widget.onChanged(jsonEncode(mapDTO).toString()); - widget.onChanged(mapDTO); - } - ), - GeolocInputContainer( - label: "Point de centrage:", - initialValue: mapDTO.centerLatitude != null && mapDTO.centerLongitude != null ? LatLong(double.parse(mapDTO.centerLatitude!), double.parse(mapDTO.centerLongitude!)) : null, - color: kPrimaryColor, - onChanged: (LatLong? localisation) { - if(localisation != null) { - mapDTO.centerLongitude = localisation.longitude.toString(); - mapDTO.centerLatitude = localisation.latitude.toString(); - } - //widget.onChanged(jsonEncode(mapDTO).toString()); - widget.onChanged(mapDTO); - }, - isSmall: true - ), - // Icon - ResourceInputContainer( - label: "Icône:", - initialValue: mapDTO.iconResourceId, - color: kPrimaryColor, - imageFit: BoxFit.contain, - onChanged: (ResourceDTO resource) { - if(resource.id == null) { - mapDTO.iconSource = null; - mapDTO.iconResourceId = null; - } else { - mapDTO.iconResourceId = resource.id; - mapDTO.iconSource = resource.url; - } - //widget.onChanged(jsonEncode(mapDTO).toString()); - widget.onChanged(mapDTO); - }, - isSmall: true - ), - ] - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - if(mapDTO.mapProvider == MapProvider.Google) - // Map Type - DropDownInputContainer( - label: "Type:", - values: map_types, - initialValue: mapType, - onChange: (String? value) { - mapDTO.mapType = MapTypeApp.fromJson(value); - //widget.onChanged(jsonEncode(mapDTO).toString()); - widget.onChanged(mapDTO); - }, - ), - if(mapDTO.mapProvider == MapProvider.MapBox) - DropDownInputContainer( - label: "Type:", - values: map_types_mapBox, - initialValue: mapTypeMapBox, - onChange: (String? value) { - mapDTO.mapTypeMapbox = MapTypeMapBox.fromJson(value); - //widget.onChanged(jsonEncode(mapDTO).toString()); - widget.onChanged(mapDTO); - }, - ), - // Zoom - SliderInputContainer( - label: "Zoom:", - initialValue: mapDTO.zoom != null ? mapDTO.zoom!.toDouble() : 18, - color: kPrimaryColor, - min: 0, - max: 30, - onChanged: (double value) { - mapDTO.zoom = value.toInt(); - //widget.onChanged(jsonEncode(mapDTO).toString()); - widget.onChanged(mapDTO); - }, - ), Container( - height: 70, - child: CategoryInputContainer( - label: "Catégories :", - initialValue: mapDTO.categories != null ? mapDTO.categories! : [], - color: kPrimaryColor, - onChanged: (List? value) { - if(value != null) { - mapDTO.categories = value; - - //update point's category - if(mapDTO.points != null) { - mapDTO.points!.forEach((p) { - // Check if category still exist - Delete - if(p.categorieId != null && !mapDTO.categories!.map((c) => c.id).any((e) => e != null && e == p.categorieId)) - { - p.categorieId = null; - } - }); - } - widget.onChanged(mapDTO); - } - }, + height: size.height * 0.25, + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceAround, + children: [ + SingleSelectContainer( + label: "Service :", + color: Colors.black, + initialValue: mapProviderIn, + inputValues: map_providers, + onChanged: (String value) { + switch (value) { + case "Google": + mapDTO.mapProvider = + MapProvider.Google; + break; + case "MapBox": + mapDTO.mapProvider = + MapProvider.MapBox; + break; + } + widget.onChanged(mapDTO); + }), + GeolocInputContainer( + label: "Point de centrage:", + initialValue: + mapDTO.centerLatitude != null && + mapDTO.centerLongitude != null + ? LatLong( + double.parse( + mapDTO.centerLatitude!), + double.parse( + mapDTO.centerLongitude!)) + : null, + color: kPrimaryColor, + onChanged: (LatLong? localisation) { + if (localisation != null) { + mapDTO.centerLongitude = + localisation.longitude.toString(); + mapDTO.centerLatitude = + localisation.latitude.toString(); + } + widget.onChanged(mapDTO); + }, + isSmall: true), + ResourceInputContainer( + label: "Icône:", + initialValue: mapDTO.iconResourceId, + color: kPrimaryColor, + imageFit: BoxFit.contain, + onChanged: (ResourceDTO resource) { + if (resource.id == null) { + mapDTO.iconSource = null; + mapDTO.iconResourceId = null; + } else { + mapDTO.iconResourceId = resource.id; + mapDTO.iconSource = resource.url; + } + widget.onChanged(mapDTO); + }, + isSmall: true), + ]), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + if (mapDTO.mapProvider == MapProvider.Google) + DropDownInputContainer( + label: "Type:", + values: map_types, + initialValue: mapType, + onChange: (String? value) { + mapDTO.mapType = + MapTypeApp.fromJson(value); + widget.onChanged(mapDTO); + }, + ), + if (mapDTO.mapProvider == MapProvider.MapBox) + DropDownInputContainer( + label: "Type:", + values: map_types_mapBox, + initialValue: mapTypeMapBox, + onChange: (String? value) { + mapDTO.mapTypeMapbox = + MapTypeMapBox.fromJson(value); + widget.onChanged(mapDTO); + }, + ), + SliderInputContainer( + label: "Zoom:", + initialValue: mapDTO.zoom != null + ? mapDTO.zoom!.toDouble() + : 18, + color: kPrimaryColor, + min: 0, + max: 30, + onChanged: (double value) { + mapDTO.zoom = value.toInt(); + widget.onChanged(mapDTO); + }, + ), + Container( + height: 70, + child: CategoryInputContainer( + label: "Catégories :", + initialValue: mapDTO.categories != null + ? mapDTO.categories! + : [], + color: kPrimaryColor, + onChanged: (List? value) { + if (value != null) { + mapDTO.categories = value; + if (mapDTO.points != null) { + mapDTO.points!.forEach((p) { + if (p.categorieId != null && + !mapDTO.categories! + .map((c) => c.id) + .any((e) => + e != null && + e == p.categorieId)) { + p.categorieId = null; + } + }); + } + widget.onChanged(mapDTO); + } + }, + ), + ) + ], + ), + ], ), - ) + ), + FutureBuilder( + future: getGeoPoints( + (appContext.getContext() as ManagerAppContext) + .clientAPI!), + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == + ConnectionState.waiting) { + return Center(child: CommonLoader()); + } else { + if (snapshot.connectionState == + ConnectionState.done) { + mapDTO.points = snapshot.data; + pointsToShow = mapDTO.points!; + pointsToShow.sort((a, b) => a.title! + .firstWhere((t) => t.language == 'FR') + .value! + .toLowerCase() + .compareTo(b.title! + .firstWhere((t) => t.language == 'FR') + .value! + .toLowerCase())); + selectedCategoriesNotifier.value = mapDTO + .categories! + .map((categorie) => categorie.id!) + .toList(); + + return Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(25), + border: Border.all( + width: 1.5, color: kSecond)), + child: Stack(children: [ + Container( + constraints: + BoxConstraints(minHeight: 100), + child: Padding( + padding: const EdgeInsets.only( + top: 95, + left: 10, + right: 10, + bottom: 10), + child: + ValueListenableBuilder( + valueListenable: + searchNotifier, + builder: (context, + searchValue, child) { + return ValueListenableBuilder< + List?>( + valueListenable: + selectedCategoriesNotifier, + builder: (context, + selectedCategories, + child) { + if (selectedCategories == + null || + selectedCategories + .length == + 0) { + pointsToShow = mapDTO + .points! + .where((point) => + point + .categorieId == + null) + .toList(); + } else { + pointsToShow = mapDTO + .points! + .where((point) => + selectedCategories.any((tps) => + point.categorieId == + null || + point.categorieId == + tps)) + .toList(); + } + + pointsToShow = searchValue != + null && + searchValue + .trim() + .isNotEmpty + ? pointsToShow + .where((GeoPointDTO pointGeo) => removeDiacritics(pointGeo + .title! + .firstWhere((t) => + t.language == + "FR") + .value! + .toUpperCase()) + .contains( + removeDiacritics( + searchValue.toUpperCase()))) + .toList() + : pointsToShow; + + return GridView + .builder( + shrinkWrap: + true, + gridDelegate: + new SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: + 8), + itemCount: + pointsToShow + .length, + itemBuilder: + (BuildContext + context, + int index) { + return Container( + decoration: boxDecoration( + pointsToShow[ + index], + appContext), + padding: + const EdgeInsets + .all( + 5), + margin: EdgeInsets.symmetric( + vertical: + 10, + horizontal: + 10), + child: getElement( + index, + pointsToShow[ + index], + size, + appContext), + ); + }); + }); + }), + ), + ), + Positioned( + top: 10, + left: 10, + child: Text( + "Points géographiques", + style: TextStyle(fontSize: 15), + ), + ), + Positioned( + top: 10, + left: 175, + child: MultiSelectContainer( + label: null, + color: kSecond, + width: size.width * 0.45, + initialValue: mapDTO.categories! + .where((cat) => + selectedCategoriesNotifier + .value! + .contains(cat.id)) + .map((categorie) => categorie + .label! + .firstWhere((element) => + element.language == + 'FR') + .value!) + .toList(), + isMultiple: true, + isHTMLLabel: true, + values: mapDTO.categories! + .map((categorie) => categorie + .label! + .firstWhere((element) => + element.language == + 'FR') + .value!) + .toList(), + onChanged: (value) { + var tempOutput = + new List.from(value); + selectedCategoriesNotifier.value = + mapDTO.categories! + .where((c) => tempOutput + .contains(c.label! + .firstWhere( + (element) => + element + .language == + 'FR') + .value!)) + .map((cat) => cat.id!) + .toList(); + }, + )), + Positioned( + top: 0, + right: 150, + child: Container( + height: size.height * 0.1, + constraints: + BoxConstraints(minHeight: 85), + child: StringInputContainer( + label: "Recherche:", + isSmall: true, + fontSize: 15, + fontSizeText: 15, + onChanged: (String value) { + searchNotifier.value = value; + }, + ), + ), + ), + Positioned( + top: 10, + right: 10, + child: InkWell( + onTap: () { + showNewOrUpdateGeoPoint( + mapDTO, null, + (GeoPointDTO geoPoint) async { + try { + await (appContext.getContext() + as ManagerAppContext) + .clientAPI! + .sectionMapApi! + .sectionMapCreate( + mapDTO.id!, geoPoint); + showNotification( + kSuccess, + kWhite, + 'Le point géographique a été créé avec succès', + context, + null); + setState(() { + // refresh ui + print("Refresh UI"); + }); + } catch (e) { + showNotification( + kError, + kWhite, + 'Une erreur est survenue lors de la création du point géographique', + context, + null); + } + }, appContext, context); + }, + child: Container( + height: MediaQuery.of(context) + .size + .width * + 0.04, + width: MediaQuery.of(context) + .size + .width * + 0.04, + child: Icon( + Icons.add, + color: kTextLightColor, + size: 30.0, + ), + decoration: BoxDecoration( + color: kSuccess, + shape: BoxShape.rectangle, + borderRadius: + BorderRadius.circular(20.0), + boxShadow: [ + BoxShadow( + color: kSecond, + spreadRadius: 0.5, + blurRadius: 5, + offset: Offset(0, + 1.5), // changes position of shadow + ), + ], + ), + ), + ), + ) + ]), + ), + ); + } else { + return Center( + child: Text( + "Une erreur est survenue lors de la récupération des points géographiques"), + ); + } + } + }), ], ), - ], - ), + ), + // Tab 2: Parcours + ParcoursConfig( + initialValue: mapDTO.guidedPaths ?? [], + parentId: mapDTO.id!, + isEvent: false, + onChanged: (paths) { + setState(() { + mapDTO.guidedPaths = paths; + widget.onChanged(mapDTO); + }); + }, + ), + ], ), - FutureBuilder( - future: getGeoPoints((appContext.getContext() as ManagerAppContext).clientAPI!), - builder: (context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return Center(child: CommonLoader()); - } else { - if(snapshot.connectionState == ConnectionState.done) - { - mapDTO.points = snapshot.data; - pointsToShow = mapDTO.points!; - pointsToShow.sort((a, b) => a.title!.firstWhere((t) => t.language == 'FR').value!.toLowerCase().compareTo(b.title!.firstWhere((t) => t.language == 'FR').value!.toLowerCase())); - selectedCategoriesNotifier.value = mapDTO.categories!.map((categorie) => categorie.id!).toList(); - - return Padding( - padding: const EdgeInsets.all(8.0), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(25), - border: Border.all(width: 1.5, color: kSecond) - ), - child: Stack( - children: [ - Container( - constraints: BoxConstraints(minHeight: 100), - child: Padding( - padding: const EdgeInsets.only(top: 95, left: 10, right: 10, bottom: 10), - child: ValueListenableBuilder( - valueListenable: searchNotifier, - builder: (context, searchValue, child) { - return ValueListenableBuilder?>( - valueListenable: selectedCategoriesNotifier, - builder: (context, selectedCategories, child) { - if(selectedCategories == null || selectedCategories.length == 0) { - pointsToShow = mapDTO.points!.where((point) => point.categorieId == null).toList(); - } else { - pointsToShow = mapDTO.points!.where((point) => selectedCategories.any((tps) => point.categorieId == null || point.categorieId == tps)).toList(); - } - - pointsToShow = searchValue != null && searchValue.trim().isNotEmpty ? pointsToShow.where((GeoPointDTO pointGeo) => removeDiacritics(pointGeo.title!.firstWhere((t) => t.language == "FR").value!.toUpperCase()).contains(removeDiacritics(searchValue.toUpperCase()))).toList() : pointsToShow; - - return GridView.builder( - shrinkWrap: true, - gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 8), - itemCount: pointsToShow.length, - itemBuilder: (BuildContext context, int index) { - return - Container( - decoration: boxDecoration(pointsToShow[index], appContext), - padding: const EdgeInsets.all(5), - margin: EdgeInsets.symmetric(vertical: 10, horizontal: 10), - child: getElement(index, pointsToShow[index], size, appContext), - ); - } - ); - } - ); - } - ), - ), - ), - Positioned( - top: 10, - left: 10, - child: Text( - "Points géographiques", - style: TextStyle(fontSize: 15), - ), - ), - Positioned( - top: 10, - left: 175, - child: MultiSelectContainer( - label: null, - color: kSecond, - width: size.width * 0.45, - initialValue: mapDTO.categories!.where((cat) => selectedCategoriesNotifier.value!.contains(cat.id)).map((categorie) => categorie.label!.firstWhere((element) => element.language == 'FR').value!).toList(), - isMultiple: true, - isHTMLLabel : true, - values: mapDTO.categories!.map((categorie) => categorie.label!.firstWhere((element) => element.language == 'FR').value!).toList(), - onChanged: (value) { - var tempOutput = new List.from(value); - selectedCategoriesNotifier.value = mapDTO.categories!.where((c) => tempOutput.contains(c.label!.firstWhere((element) => element.language == 'FR').value!)).map((cat) => cat.id!).toList(); - }, - ) - ), - Positioned( - top: 0, - right: 150, - child: Container( - height: size.height*0.1, - constraints: BoxConstraints(minHeight: 85), - child: StringInputContainer( - label: "Recherche:", - isSmall: true, - fontSize: 15, - fontSizeText: 15, - onChanged: (String value) { - searchNotifier.value = value; - }, - ), - ), - ), - Positioned( - top: 10, - right: 10, - child: InkWell( - onTap: () { - showNewOrUpdateGeoPoint( - mapDTO, - null, - (GeoPointDTO geoPoint) async { - /*setState(() { - mapDTO.points!.add(geoPoint); - mapDTO.points!.sort((a, b) => a.title!.firstWhere((t) => t.language == 'FR').value!.toLowerCase().compareTo(b.title!.firstWhere((t) => t.language == 'FR').value!.toLowerCase())); - - if(selectedCategoriesNotifier.value == null || selectedCategoriesNotifier.value!.length == 0) { - //pointsToShow = mapDTO.points!.where((point) => point.categorie == null).toList(); - pointsToShow = mapDTO.points!.where((point) => point.categorieId == null).toList(); - } else { - //pointsToShow = mapDTO.points!.where((point) => selectedCategories!.any((tps) => point.categorie == null || point.categorie?.label?.firstWhere((lab) => lab.language == 'FR').value == tps)).toList(); - pointsToShow = mapDTO.points!.where((point) => selectedCategoriesNotifier.value!.any((tps) => point.categorieId == null || point.categorieId == tps)).toList(); - } - - //widget.onChanged(jsonEncode(mapDTO).toString()); - widget.onChanged(mapDTO); - });*/ - - try { - await (appContext.getContext() as ManagerAppContext).clientAPI!.sectionMapApi!.sectionMapCreate(mapDTO.id!, geoPoint); - showNotification(kSuccess, kWhite, 'Le point géographique a été créé avec succès', context, null); - setState(() { - // refresh ui - print("Refresh UI"); - }); - } catch(e) { - showNotification(kError, kWhite, 'Une erreur est survenue lors de la création du point géographique', context, null); - } - }, - appContext, - context); - }, - child: Container( - height: MediaQuery.of(context).size.width * 0.04, - width: MediaQuery.of(context).size.width * 0.04, - child: Icon( - Icons.add, - color: kTextLightColor, - size: 30.0, - ), - decoration: BoxDecoration( - color: kSuccess, - shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(20.0), - boxShadow: [ - BoxShadow( - color: kSecond, - spreadRadius: 0.5, - blurRadius: 5, - offset: Offset(0, 1.5), // changes position of shadow - ), - ], - ), - ), - ), - ) - ]), - ), - ); - } else { - return Center(child: Text("Une erreur est survenue lors de la récupération des points géographiques"),); - } - } - } - ), - ], - ), - ); + ), + ], + ), + ); } Future?> getGeoPoints(Client client) async { - List? geoPoints = await client.sectionMapApi!.sectionMapGetAllGeoPointsFromSection(widget.initialValue.id!); + List? geoPoints = await client.sectionMapApi! + .sectionMapGetAllGeoPointsFromSection(widget.initialValue.id!); return geoPoints ?? []; } @@ -455,13 +614,15 @@ class _MapConfigState extends State { child: Padding( padding: const EdgeInsets.all(8.0), child: HtmlWidget( - point != null ? point.title == null ? "" : point.title![0].value! : "", + point != null + ? point.title == null + ? "" + : point.title![0].value! + : "", //textAlign: TextAlign.left, customStylesBuilder: (element) { - return {'text-align': 'center'}; - }, - textStyle: TextStyle(fontSize: 20) - ), + return {'text-align': 'center'}; + }, textStyle: TextStyle(fontSize: 20)), ), ), Positioned( @@ -478,22 +639,33 @@ class _MapConfigState extends State { left: 0, child: InkWell( onTap: () { - showNewOrUpdateGeoPoint( - mapDTO, - pointsToShow[index], - (GeoPointDTO geoPoint) async { - try { - await (appContext.getContext() as ManagerAppContext).clientAPI!.sectionMapApi!.sectionMapUpdate(geoPoint); - showNotification(kSuccess, kWhite, 'Le point géographique a été mis à jour avec succès', context, null); - setState(() { - // refresh ui - print("Refresh UI"); - }); - } catch(e) { - showNotification(kError, kWhite, 'Une erreur est survenue lors de la mise à jour du point géographique', context, null); - } + showNewOrUpdateGeoPoint(mapDTO, pointsToShow[index], + (GeoPointDTO geoPoint) async { + try { + await (appContext.getContext() as ManagerAppContext) + .clientAPI! + .sectionMapApi! + .sectionMapUpdate(geoPoint); + showNotification( + kSuccess, + kWhite, + 'Le point géographique a été mis à jour avec succès', + context, + null); + setState(() { + // refresh ui + print("Refresh UI"); + }); + } catch (e) { + showNotification( + kError, + kWhite, + 'Une erreur est survenue lors de la mise à jour du point géographique', + context, + null); + } - /*setState(() { + /*setState(() { var pointToUpdate = pointsToShow[index]; var pointToUpdateMapDTOPoints = mapDTO.points!.firstWhere((p) => p.longitude == pointToUpdate.longitude && p.latitude == pointToUpdate.latitude); var mapDTOPointsIndex = mapDTO.points!.indexOf(pointToUpdateMapDTOPoints); @@ -503,16 +675,13 @@ class _MapConfigState extends State { //widget.onChanged(jsonEncode(mapDTO).toString()); widget.onChanged(mapDTO); });*/ - }, - appContext, - context); + }, appContext, context); }, child: Icon( Icons.edit, color: kPrimaryColor, size: 20.0, - ) - ), + )), ), Positioned( bottom: 0, @@ -521,30 +690,38 @@ class _MapConfigState extends State { onTap: () async { showConfirmationDialog( "Êtes-vous sûr de vouloir supprimer ce point géographique ?", - () {}, - () async { - - try { - var pointToRemove = pointsToShow[index]; - (appContext.getContext() as ManagerAppContext).clientAPI!.sectionMapApi!.sectionMapDelete(pointToRemove.id!); - showNotification(kSuccess, kWhite, 'Le point géographique a été supprimé avec succès', context, null); - // refresh UI - setState(() { - print("Refresh UI"); - }); - } catch(e) { - showNotification(kError, kWhite, 'Une erreur est survenue lors de la suppression du point géographique', context, null); - } - }, - context - ); + () {}, () async { + try { + var pointToRemove = pointsToShow[index]; + (appContext.getContext() as ManagerAppContext) + .clientAPI! + .sectionMapApi! + .sectionMapDelete(pointToRemove.id!); + showNotification( + kSuccess, + kWhite, + 'Le point géographique a été supprimé avec succès', + context, + null); + // refresh UI + setState(() { + print("Refresh UI"); + }); + } catch (e) { + showNotification( + kError, + kWhite, + 'Une erreur est survenue lors de la suppression du point géographique', + context, + null); + } + }, context); }, child: Icon( Icons.delete, color: kError, size: 20.0, - ) - ), + )), ) ], ), @@ -552,7 +729,6 @@ class _MapConfigState extends State { } } - boxDecoration(GeoPointDTO geoPointDTO, appContext) { return BoxDecoration( color: kBackgroundColor, @@ -568,4 +744,4 @@ boxDecoration(GeoPointDTO geoPointDTO, appContext) { ), ], ); -} \ No newline at end of file +} diff --git a/lib/Screens/Configurations/Section/SubSection/Map/showNewOrUpdateGeoPoint.dart b/lib/Screens/Configurations/Section/SubSection/Map/showNewOrUpdateGeoPoint.dart index 91c316e..396cbf4 100644 --- a/lib/Screens/Configurations/Section/SubSection/Map/showNewOrUpdateGeoPoint.dart +++ b/lib/Screens/Configurations/Section/SubSection/Map/showNewOrUpdateGeoPoint.dart @@ -1,19 +1,18 @@ import 'package:flutter/material.dart'; -import 'package:location_picker_flutter_map/location_picker_flutter_map.dart'; +import 'package:manager_app/Components/geometry_input_container.dart'; import 'package:manager_app/Components/dropDown_input_container_categories.dart'; -import 'package:manager_app/Components/common_loader.dart'; import 'package:manager_app/Components/multi_string_input_container.dart'; import 'package:manager_app/Components/resource_input_container.dart'; import 'package:manager_app/Components/rounded_button.dart'; -import 'package:manager_app/Components/string_input_container.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Map/geopoint_image_list.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/constants.dart'; import 'package:manager_api_new/api.dart'; -void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO, Function getResult, AppContext appContext, BuildContext context) { - GeoPointDTO geoPointDTO = new GeoPointDTO(); +void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO, + Function getResult, AppContext appContext, BuildContext context) { + GeoPointDTO geoPointDTO = GeoPointDTO(); if (inputGeoPointDTO != null) { geoPointDTO = inputGeoPointDTO; @@ -21,370 +20,334 @@ void showNewOrUpdateGeoPoint(MapDTO mapDTO, GeoPointDTO? inputGeoPointDTO, Funct geoPointDTO.title = []; geoPointDTO.description = []; geoPointDTO.contents = []; + geoPointDTO.schedules = []; + geoPointDTO.prices = []; + geoPointDTO.phone = []; + geoPointDTO.email = []; + geoPointDTO.site = []; ManagerAppContext managerAppContext = appContext.getContext(); managerAppContext.selectedConfiguration!.languages!.forEach((element) { - var translationDTO = new TranslationDTO(); + var translationDTO = TranslationDTO(); translationDTO.language = element; translationDTO.value = ""; geoPointDTO.title!.add(translationDTO); geoPointDTO.description!.add(translationDTO); + geoPointDTO.schedules!.add(translationDTO); + geoPointDTO.prices!.add(translationDTO); + geoPointDTO.phone!.add(translationDTO); + geoPointDTO.email!.add(translationDTO); + geoPointDTO.site!.add(translationDTO); }); } - var defaultPosition = LatLong(50.429333, 4.891434); // Default Namur - var pointPosition; - - if(geoPointDTO.geometry != null) { - switch(geoPointDTO.geometry!.type) { - case "Point": - var coordinates = geoPointDTO.geometry!.coordinates as List; - pointPosition = LatLong(coordinates.first, coordinates.last); - break; - case "LineString": - pointPosition = LatLong(50.429333, 4.891434); // TODO default Namur - break; - case "Polygon": - pointPosition = LatLong(50.429333, 4.891434); // TODO default Namur - break; - } - } - - LatLong initPosition = geoPointDTO.geometry == null ? defaultPosition : pointPosition; - - Size size = MediaQuery.of(context).size; showDialog( - builder: (BuildContext context) => AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(20.0)) - ), - content: Container( - width: size.width *0.85, - child: SingleChildScrollView( - child: Column( - children: [ - Text("Point géographique", style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400)), - Column( - children: [ - Container( - width: size.width *0.75, - height: 350, - child: FlutterLocationPicker( - initZoom: 14, - initPosition: initPosition, - minZoomLevel: 0, - maxZoomLevel: 17, - markerIcon: const Icon( - Icons.location_pin, - color: kPrimaryColor, - size: 50, - ), - loadingWidget: CommonLoader(iconSize: 40.0), - searchBarHintColor: kPrimaryColor, - mapLoadingBackgroundColor: kSecond, - zoomButtonsBackgroundColor: kPrimaryColor, - zoomButtonsColor: Colors.white, - locationButtonBackgroundColor: kPrimaryColor, - locationButtonsColor: Colors.white, - countryFilter: "be, fr", - trackMyPosition: false, - searchBarHintText: "Chercher une localisation", - searchBarTextColor: Colors.black, - searchBarBackgroundColor: Colors.white, - showSelectLocationButton : true, - selectLocationButtonText: "Choisir cette localisation", - selectedLocationButtonTextstyle: const TextStyle(fontSize: 18, color: kPrimaryColor), - mapLanguage: 'fr', - onError: (e) => print(e), - selectLocationButtonLeadingIcon: const Icon(Icons.check, color: kPrimaryColor), - onPicked: (pickedData) { - geoPointDTO.geometry = new GeometryDTO(type: "Point", coordinates: [pickedData.latLong.latitude, pickedData.latLong.longitude]); - }, - onChanged: (pickedData) { - print("onChanged"); - geoPointDTO.geometry = new GeometryDTO(type: "Point", coordinates: [pickedData.latLong.latitude, pickedData.latLong.longitude]); - /*print(pickedData.latLong.latitude); - print(pickedData.latLong.longitude); - print(pickedData.address); - print(pickedData.addressData);*/ - }, - showContributorBadgeForOSM: false, - ), - ), - /*Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - SizedBox( - height: 100, - child: StringInputContainer( - isSmall: true, - label: "Latitude (#.#):", - initialValue: geoPointDTO.latitude, - onChanged: (value) { - geoPointDTO.latitude = value; - }, - ), - ), - SizedBox( - height: 100, - child: StringInputContainer( - isSmall: true, - label: "Longitude (#.#):", - initialValue: geoPointDTO.longitude, - onChanged: (value) { - geoPointDTO.longitude = value; - }, - ), - ) - ], - ),*/ - Container( - height: 100, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (context, setState) { + final double screenWidth = MediaQuery.of(context).size.width; + final double screenHeight = MediaQuery.of(context).size.height; + final double dialogWidth = screenWidth * 0.88; + final double contentWidth = + dialogWidth - 48; // 24px padding each side + final double thirdWidth = (contentWidth - 40) / 3; + + return Dialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Container( + width: dialogWidth, + constraints: BoxConstraints(maxHeight: screenHeight * 0.9), + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Titre du dialog + Text( + "Point géographique / Zone", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + color: kPrimaryColor), + ), + SizedBox(height: 16), + // Corps scrollable + Flexible( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - constraints: BoxConstraints(minHeight: 50, maxHeight: 80), - child: MultiStringInputContainer( - label: "Titre affiché:", - modalLabel: "Titre", - fontSize: 20, - isHTML: true, - color: kPrimaryColor, - initialValue: geoPointDTO.title != null ? geoPointDTO.title! : [], - onGetResult: (value) { - if (geoPointDTO.title != value) { + // Géométrie + GeometryInputContainer( + label: "Géométrie (Point/Ligne/Zone) :", + initialGeometry: geoPointDTO.geometry, + initialColor: geoPointDTO.polyColor, + onSave: (geometry, color) { + setState(() { + geoPointDTO.geometry = geometry; + geoPointDTO.polyColor = color; + }); + }, + ), + SizedBox(height: 12), + // Ligne 1 : Titre | Description | Site + Row( + children: [ + SizedBox( + width: thirdWidth, + child: MultiStringInputContainer( + label: "Titre :", + modalLabel: "Titre", + fontSize: 16, + isHTML: true, + color: kPrimaryColor, + initialValue: geoPointDTO.title ?? [], + onGetResult: (value) { geoPointDTO.title = value; - } - }, - maxLines: 1, - isTitle: true - ), - ), - Container( - constraints: BoxConstraints(minHeight: 50, maxHeight: 80), - child: MultiStringInputContainer( - label: "Description affichée:", - modalLabel: "Description", - fontSize: 20, - isHTML: true, - color: kPrimaryColor, - initialValue: geoPointDTO.description != null ? geoPointDTO.description! : [], - isMandatory: false, - onGetResult: (value) { - if (geoPointDTO.description != value) { - geoPointDTO.description = value; - } - }, - maxLines: 1, - isTitle: false - ), - ), - Container( - constraints: BoxConstraints(minHeight: 50, maxHeight: 80), - child: MultiStringInputContainer( - label: "Site:", - modalLabel: "Site web", - fontSize: 20, - isHTML: true, - color: kPrimaryColor, - initialValue: geoPointDTO.site != null ? geoPointDTO.site! : [], - isMandatory: false, - onGetResult: (value) { - if (geoPointDTO.site != value) { - geoPointDTO.site = value; - } - }, - maxLines: 1, - isTitle: true - ), - ), - ], - ), - ), - Container( - height: 100, - width: double.infinity, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Container( - constraints: BoxConstraints(minHeight: 50, maxHeight: 80), - child: MultiStringInputContainer( - label: "Prix:", - modalLabel: "Prix", - fontSize: 20, - isHTML: true, - color: kPrimaryColor, - initialValue: geoPointDTO.prices != null ? geoPointDTO.prices! : [], - isMandatory: false, - onGetResult: (value) { - if (geoPointDTO.prices != value) { - geoPointDTO.prices = value; - } - }, - maxLines: 1, - isTitle: false - ), - ), - Container( - constraints: BoxConstraints(minHeight: 50, maxHeight: 80), - child: MultiStringInputContainer( - label: "Téléphone:", - modalLabel: "Téléphone", - fontSize: 20, - isHTML: true, - color: kPrimaryColor, - initialValue: geoPointDTO.phone != null ? geoPointDTO.phone! : [], - isMandatory: false, - onGetResult: (value) { - if (geoPointDTO.phone != value) { - geoPointDTO.phone = value; - } - }, - maxLines: 1, - isTitle: true - ), - ), - Container( - constraints: BoxConstraints(minHeight: 50, maxHeight: 80), - child: MultiStringInputContainer( - label: "Email:", - modalLabel: "Email", - fontSize: 20, - isHTML: true, - color: kPrimaryColor, - initialValue: geoPointDTO.email != null ? geoPointDTO.email! : [], - isMandatory: false, - onGetResult: (value) { - if (geoPointDTO.email != value) { - geoPointDTO.email = value; - } - }, - maxLines: 1, - isTitle: true - ), - ), - ], - ), - ), - Container( - height: 100, - width: double.infinity, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - ResourceInputContainer( - label: "Image principal:", - initialValue: geoPointDTO.imageResourceId != null ? geoPointDTO.imageResourceId : null, - color: kPrimaryColor, - onChanged: (ResourceDTO resource) { - if(resource.id == null) { - geoPointDTO.imageResourceId = null; - geoPointDTO.imageUrl = null; - } else { - geoPointDTO.imageResourceId = resource.id; - geoPointDTO.imageUrl = resource.url; - } - }, - ), - if(mapDTO.categories != null && mapDTO.categories!.isNotEmpty) - Container( - constraints: BoxConstraints(minHeight: 50, maxHeight: 80), - child: DropDownInputContainerCategories( - label: "Choisir une catégorie:", - categories: mapDTO.categories!, - initialValue: geoPointDTO.categorieId, - onChange: (CategorieDTO? value) { - if(value != null && value.order != -1) - { - geoPointDTO.categorieId = value.id; - } else - { - geoPointDTO.categorieId = null; - } - }, + }, + maxLines: 1, + isTitle: true, + ), ), + SizedBox(width: 20), + SizedBox( + width: thirdWidth, + child: MultiStringInputContainer( + label: "Description :", + modalLabel: "Description", + fontSize: 16, + isHTML: true, + color: kPrimaryColor, + initialValue: geoPointDTO.description ?? [], + isMandatory: false, + onGetResult: (value) { + geoPointDTO.description = value; + }, + maxLines: 1, + isTitle: false, + ), + ), + SizedBox(width: 20), + SizedBox( + width: thirdWidth, + child: MultiStringInputContainer( + label: "Site :", + modalLabel: "Site web", + fontSize: 16, + isHTML: true, + color: kPrimaryColor, + initialValue: geoPointDTO.site ?? [], + isMandatory: false, + onGetResult: (value) { + geoPointDTO.site = value; + }, + maxLines: 1, + isTitle: true, + ), + ), + ], + ), + SizedBox(height: 12), + // Ligne 2 : Prix | Tel | Email + Row( + children: [ + SizedBox( + width: thirdWidth, + child: MultiStringInputContainer( + label: "Prix :", + modalLabel: "Prix", + fontSize: 16, + isHTML: true, + color: kPrimaryColor, + initialValue: geoPointDTO.prices ?? [], + isMandatory: false, + onGetResult: (value) { + geoPointDTO.prices = value; + }, + maxLines: 1, + isTitle: false, + ), + ), + SizedBox(width: 20), + SizedBox( + width: thirdWidth, + child: MultiStringInputContainer( + label: "Tel :", + modalLabel: "Téléphone", + fontSize: 16, + isHTML: true, + color: kPrimaryColor, + initialValue: geoPointDTO.phone ?? [], + isMandatory: false, + onGetResult: (value) { + geoPointDTO.phone = value; + }, + maxLines: 1, + isTitle: true, + ), + ), + SizedBox(width: 20), + SizedBox( + width: thirdWidth, + child: MultiStringInputContainer( + label: "Email :", + modalLabel: "Email", + fontSize: 16, + isHTML: true, + color: kPrimaryColor, + initialValue: geoPointDTO.email ?? [], + isMandatory: false, + onGetResult: (value) { + geoPointDTO.email = value; + }, + maxLines: 1, + isTitle: true, + ), + ), + ], + ), + SizedBox(height: 12), + // Ligne 3 : Horaires | Image | Catégorie + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: thirdWidth, + child: MultiStringInputContainer( + label: "Horaires :", + modalLabel: "Horaires d'ouverture", + fontSize: 16, + isHTML: true, + color: kPrimaryColor, + initialValue: geoPointDTO.schedules ?? [], + isMandatory: false, + onGetResult: (value) { + geoPointDTO.schedules = value; + }, + maxLines: 1, + isTitle: false, + ), + ), + SizedBox(width: 20), + SizedBox( + width: thirdWidth, + child: ResourceInputContainer( + label: "Image :", + initialValue: geoPointDTO.imageResourceId, + color: kPrimaryColor, + onChanged: (ResourceDTO resource) { + if (resource.id == null) { + geoPointDTO.imageResourceId = null; + geoPointDTO.imageUrl = null; + } else { + geoPointDTO.imageResourceId = resource.id; + geoPointDTO.imageUrl = resource.url; + } + }, + ), + ), + SizedBox(width: 20), + if (mapDTO.categories != null && + mapDTO.categories!.isNotEmpty) + SizedBox( + width: thirdWidth, + child: DropDownInputContainerCategories( + label: "Catégorie :", + categories: mapDTO.categories!, + initialValue: geoPointDTO.categorieId, + onChange: (CategorieDTO? value) { + if (value != null && value.order != -1) { + geoPointDTO.categorieId = value.id; + } else { + geoPointDTO.categorieId = null; + } + }, + ), + ) + else + SizedBox(width: thirdWidth), + ], + ), + SizedBox(height: 12), + // Liste de contenus + Container( + height: screenHeight * 0.28, + decoration: BoxDecoration( + color: kWhite, + shape: BoxShape.rectangle, + border: Border.all(width: 1.5, color: kSecond), + borderRadius: BorderRadius.circular(10.0), + boxShadow: [ + BoxShadow( + color: kSecond, + spreadRadius: 0.5, + blurRadius: 5, + offset: Offset(0, 1.5), + ), + ], + ), + child: GeoPointContentList( + contents: geoPointDTO.contents!, + onChanged: (List contentsOutput) { + geoPointDTO.contents = contentsOutput; + }, ), - ], - ), - ), - Container( - height: size.height * 0.33, - decoration: BoxDecoration( - color: kWhite, - shape: BoxShape.rectangle, - border: Border.all(width: 1.5, color: kSecond), - borderRadius: BorderRadius.circular(10.0), - boxShadow: [ - BoxShadow( - color: kSecond, - spreadRadius: 0.5, - blurRadius: 5, - offset: Offset(0, 1.5), // changes position of shadow ), ], ), - child: GeoPointContentList( - contents: geoPointDTO.contents!, - onChanged: (List contentsOutput) { - geoPointDTO.contents = contentsOutput; - }, - ), ), - ], - ), - ], + ), + SizedBox(height: 16), + // Boutons + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + height: 46, + child: RoundedButton( + text: "Annuler", + icon: Icons.undo, + color: kSecond, + press: () { + if (inputGeoPointDTO != null) { + geoPointDTO.contents = inputGeoPointDTO.contents; + } + Navigator.of(context).pop(); + }, + fontSize: 15, + horizontal: 24, + ), + ), + SizedBox(width: 12), + SizedBox( + height: 46, + child: RoundedButton( + text: + geoPointDTO.id != null ? "Sauvegarder" : "Créer", + icon: Icons.check, + color: kPrimaryColor, + textColor: kWhite, + press: () { + if (geoPointDTO.geometry != null && + geoPointDTO.geometry!.coordinates != null) { + getResult(geoPointDTO); + Navigator.of(context).pop(); + } + }, + fontSize: 15, + horizontal: 24, + ), + ), + ], + ), + ], + ), ), - ), - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Align( - alignment: AlignmentDirectional.bottomEnd, - child: Container( - width: 175, - height: 70, - child: RoundedButton( - text: "Annuler", - icon: Icons.undo, - color: kSecond, - press: () { - if (inputGeoPointDTO != null) { - geoPointDTO.contents = inputGeoPointDTO.contents; - } - Navigator.of(context).pop(); - }, - fontSize: 20, - ), - ), - ), - Align( - alignment: AlignmentDirectional.bottomEnd, - child: Container( - width: geoPointDTO != null ? 220: 150, - height: 70, - child: RoundedButton( - text: geoPointDTO != null ? "Sauvegarder" : "Créer", - icon: Icons.check, - color: kPrimaryColor, - textColor: kWhite, - press: () { - //print("TODO"); - if (geoPointDTO.geometry != null && geoPointDTO.geometry!.coordinates != null) { - getResult(geoPointDTO); - Navigator.of(context).pop(); - } - }, - fontSize: 20, - ), - ), - ), - ], - ), - ], - ), context: context + ); + }, + ); + }, ); } diff --git a/lib/Screens/Configurations/Section/SubSection/Parcours/parcours_config.dart b/lib/Screens/Configurations/Section/SubSection/Parcours/parcours_config.dart new file mode 100644 index 0000000..f0c27f2 --- /dev/null +++ b/lib/Screens/Configurations/Section/SubSection/Parcours/parcours_config.dart @@ -0,0 +1,156 @@ +import 'package:flutter/material.dart'; +import 'package:manager_api_new/api.dart'; +import 'package:manager_app/constants.dart'; +import 'package:manager_app/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedPath.dart'; + +class ParcoursConfig extends StatefulWidget { + final List initialValue; + final String parentId; + final bool isEvent; + final bool isEscapeMode; + final ValueChanged> onChanged; + + const ParcoursConfig({ + Key? key, + required this.initialValue, + required this.parentId, + required this.isEvent, + this.isEscapeMode = false, + required this.onChanged, + }) : super(key: key); + + @override + _ParcoursConfigState createState() => _ParcoursConfigState(); +} + +class _ParcoursConfigState extends State { + late List paths; + + @override + void initState() { + super.initState(); + paths = List.from(widget.initialValue); + paths.sort((a, b) => (a.order ?? 0).compareTo(b.order ?? 0)); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Parcours Guidés", + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + ElevatedButton.icon( + icon: Icon(Icons.add), + label: Text("Ajouter un parcours"), + onPressed: () { + showNewOrUpdateGuidedPath( + context, + null, + widget.parentId, + widget.isEvent, + widget.isEscapeMode, + (newPath) { + setState(() { + newPath.order = paths.length; + paths.add(newPath); + widget.onChanged(paths); + }); + }, + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: kSuccess, foregroundColor: kWhite), + ), + ], + ), + ), + Expanded( + child: paths.isEmpty + ? Center( + child: Text("Aucun parcours configuré", + style: TextStyle(fontStyle: FontStyle.italic))) + : ReorderableListView.builder( + buildDefaultDragHandles: false, + itemCount: paths.length, + onReorder: (oldIndex, newIndex) { + setState(() { + if (newIndex > oldIndex) newIndex -= 1; + final item = paths.removeAt(oldIndex); + paths.insert(newIndex, item); + for (int i = 0; i < paths.length; i++) { + paths[i].order = i; + } + widget.onChanged(paths); + }); + }, + itemBuilder: (context, index) { + final path = paths[index]; + return Card( + key: ValueKey(path.id ?? index.toString()), + margin: EdgeInsets.symmetric(horizontal: 10, vertical: 5), + child: ListTile( + leading: CircleAvatar( + child: Text("${index + 1}"), + backgroundColor: kPrimaryColor, + foregroundColor: kWhite), + title: Text(path.title != null && path.title!.isNotEmpty + ? path.title! + .firstWhere((t) => t.language == 'FR', + orElse: () => path.title![0]) + .value ?? + "Parcours sans titre" + : "Parcours sans titre"), + subtitle: Text("${path.steps?.length ?? 0} étapes"), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(Icons.edit, color: kPrimaryColor), + onPressed: () { + showNewOrUpdateGuidedPath( + context, + path, + widget.parentId, + widget.isEvent, + widget.isEscapeMode, + (updatedPath) { + setState(() { + paths[index] = updatedPath; + widget.onChanged(paths); + }); + }, + ); + }, + ), + IconButton( + icon: Icon(Icons.delete, color: kError), + onPressed: () { + setState(() { + paths.removeAt(index); + for (int i = 0; i < paths.length; i++) { + paths[i].order = i; + } + widget.onChanged(paths); + }); + }, + ), + ReorderableDragStartListener( + index: index, + child: Icon(Icons.drag_handle), + ), + ], + ), + ), + ); + }, + ), + ), + ], + ); + } +} diff --git a/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedPath.dart b/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedPath.dart new file mode 100644 index 0000000..b3bd7a4 --- /dev/null +++ b/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedPath.dart @@ -0,0 +1,275 @@ +import 'package:flutter/material.dart'; +import 'package:manager_api_new/api.dart'; +import 'package:manager_app/constants.dart'; +import 'package:manager_app/Components/rounded_button.dart'; +import 'package:manager_app/Components/multi_string_input_container.dart'; +import 'package:manager_app/Components/check_input_container.dart'; +import 'showNewOrUpdateGuidedStep.dart'; + +void showNewOrUpdateGuidedPath( + BuildContext context, + GuidedPathDTO? path, + String parentId, + bool isEvent, + bool isEscapeMode, + Function(GuidedPathDTO) onSave, +) { + GuidedPathDTO workingPath = path != null + ? GuidedPathDTO.fromJson(path.toJson())! + : GuidedPathDTO( + title: [], + description: [], + steps: [], + order: 0, + ); + + showDialog( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (context, setState) { + final double screenWidth = MediaQuery.of(context).size.width; + final double screenHeight = MediaQuery.of(context).size.height; + final double dialogWidth = screenWidth * 0.82; + // contentWidth = dialogWidth minus the 24px padding on each side + final double contentWidth = dialogWidth - 48; + final double halfWidth = (contentWidth - 20) / 2; + final double thirdWidth = (contentWidth - 40) / 3; + + return Dialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Container( + width: dialogWidth, + constraints: BoxConstraints(maxHeight: screenHeight * 0.88), + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // -- Titre du dialog -- + Text( + path == null ? "Nouveau Parcours" : "Modifier le Parcours", + style: TextStyle( + color: kPrimaryColor, + fontSize: 20, + fontWeight: FontWeight.bold), + ), + SizedBox(height: 16), + // -- Corps scrollable -- + Flexible( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Titre + Description côte à côte + Row( + children: [ + SizedBox( + width: halfWidth, + child: MultiStringInputContainer( + label: "Titre :", + modalLabel: "Titre du parcours", + initialValue: workingPath.title ?? [], + onGetResult: (val) => + setState(() => workingPath.title = val), + maxLines: 1, + isTitle: true, + ), + ), + SizedBox(width: 20), + SizedBox( + width: halfWidth, + child: MultiStringInputContainer( + label: "Description :", + modalLabel: "Description du parcours", + initialValue: workingPath.description ?? [], + onGetResult: (val) => setState( + () => workingPath.description = val), + maxLines: 1, + isTitle: false, + ), + ), + ], + ), + Divider(height: 24), + // Options + Row( + children: [ + SizedBox( + width: thirdWidth, + child: CheckInputContainer( + label: "Linéaire :", + isChecked: workingPath.isLinear ?? false, + onChanged: (val) => setState( + () => workingPath.isLinear = val), + ), + ), + SizedBox(width: 20), + SizedBox( + width: thirdWidth, + child: CheckInputContainer( + label: "Réussite requise :", + isChecked: + workingPath.requireSuccessToAdvance ?? + false, + onChanged: (val) => setState(() => workingPath + .requireSuccessToAdvance = val), + ), + ), + SizedBox(width: 20), + SizedBox( + width: thirdWidth, + child: CheckInputContainer( + label: "Cacher les suivantes :", + isChecked: + workingPath.hideNextStepsUntilComplete ?? + false, + onChanged: (val) => setState(() => workingPath + .hideNextStepsUntilComplete = val), + ), + ), + ], + ), + Divider(height: 24), + // Étapes + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Étapes du parcours", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 15)), + IconButton( + icon: Icon(Icons.add_circle_outline, + color: kSuccess), + onPressed: () { + showNewOrUpdateGuidedStep( + context, + null, + workingPath.id ?? "temp", + isEscapeMode, + (newStep) { + setState(() { + workingPath.steps = [ + ...(workingPath.steps ?? []), + newStep + ]; + }); + }, + ); + }, + ), + ], + ), + if (workingPath.steps?.isEmpty ?? true) + Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Text( + "Aucun point/étape configuré.", + style: TextStyle( + fontStyle: FontStyle.italic, + color: Colors.grey[600]), + ), + ) + else + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: workingPath.steps!.length, + itemBuilder: (context, index) { + final step = workingPath.steps![index]; + return ListTile( + leading: + CircleAvatar(child: Text("${index + 1}")), + title: Text( + step.title != null && step.title!.isNotEmpty + ? step.title! + .firstWhere( + (t) => t.language == 'FR', + orElse: () => + step.title![0]) + .value ?? + "Étape $index" + : "Étape $index", + ), + subtitle: Text( + "${step.quizQuestions?.length ?? 0} question(s)"), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(Icons.edit, + color: kPrimaryColor), + onPressed: () { + showNewOrUpdateGuidedStep( + context, + step, + workingPath.id ?? "temp", + isEscapeMode, + (updatedStep) { + setState(() { + workingPath.steps![index] = + updatedStep; + }); + }, + ); + }, + ), + IconButton( + icon: Icon(Icons.delete, color: kError), + onPressed: () { + setState(() { + workingPath.steps!.removeAt(index); + }); + }, + ), + ], + ), + ); + }, + ), + ], + ), + ), + ), + SizedBox(height: 16), + // -- Boutons -- + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + height: 46, + child: RoundedButton( + text: "Annuler", + press: () => Navigator.pop(context), + color: kSecond, + fontSize: 15, + horizontal: 24, + ), + ), + SizedBox(width: 12), + SizedBox( + height: 46, + child: RoundedButton( + text: "Sauvegarder", + press: () { + onSave(workingPath); + Navigator.pop(context); + }, + color: kPrimaryColor, + fontSize: 15, + horizontal: 24, + ), + ), + ], + ), + ], + ), + ), + ); + }, + ); + }, + ); +} diff --git a/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedStep.dart b/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedStep.dart new file mode 100644 index 0000000..d2bc19a --- /dev/null +++ b/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedStep.dart @@ -0,0 +1,265 @@ +import 'package:flutter/material.dart'; +import 'package:manager_api_new/api.dart'; +import 'package:manager_app/Components/confirmation_dialog.dart'; +import 'package:manager_app/Components/geometry_input_container.dart'; +import 'package:manager_app/Components/multi_string_input_container.dart'; +import 'package:manager_app/Components/rounded_button.dart'; +import 'package:manager_app/constants.dart'; +import 'showNewOrUpdateQuizQuestion.dart'; + +void showNewOrUpdateGuidedStep( + BuildContext context, + GuidedStepDTO? step, + String pathId, + bool isEscapeMode, + Function(GuidedStepDTO) onSave, +) { + GuidedStepDTO workingStep = step != null + ? GuidedStepDTO.fromJson(step.toJson())! + : GuidedStepDTO( + title: [], + description: [], + quizQuestions: [], + order: 0, + ); + + // Convert EventAddressDTOGeometry to GeometryDTO via JSON for the geometry picker + GeometryDTO? _toGeometryDTO(EventAddressDTOGeometry? geo) { + if (geo == null) return null; + return GeometryDTO.fromJson(geo.toJson()); + } + + EventAddressDTOGeometry? _toEventGeometry(GeometryDTO? geo) { + if (geo == null) return null; + return EventAddressDTOGeometry.fromJson(geo.toJson()); + } + + showDialog( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (context, setState) { + final double screenWidth = MediaQuery.of(context).size.width; + final double screenHeight = MediaQuery.of(context).size.height; + final double dialogWidth = screenWidth * 0.75; + final double contentWidth = dialogWidth - 48; + final double halfWidth = (contentWidth - 20) / 2; + + return Dialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Container( + width: dialogWidth, + constraints: BoxConstraints(maxHeight: screenHeight * 0.85), + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + step == null ? "Nouvelle Étape" : "Modifier l'Étape", + style: TextStyle( + color: kPrimaryColor, + fontSize: 20, + fontWeight: FontWeight.bold), + ), + SizedBox(height: 16), + Flexible( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Titre + Description côte à côte + Row( + children: [ + SizedBox( + width: halfWidth, + child: MultiStringInputContainer( + label: "Titre :", + modalLabel: "Titre de l'étape", + initialValue: workingStep.title ?? [], + onGetResult: (val) => + setState(() => workingStep.title = val), + maxLines: 1, + isTitle: true, + ), + ), + SizedBox(width: 20), + SizedBox( + width: halfWidth, + child: MultiStringInputContainer( + label: "Description :", + modalLabel: "Description de l'étape", + initialValue: workingStep.description ?? [], + onGetResult: (val) => setState( + () => workingStep.description = val), + maxLines: 1, + isTitle: false, + ), + ), + ], + ), + Divider(height: 24), + // Géométrie — conversion JSON entre les deux types GeoDTO + GeometryInputContainer( + label: "Emplacement de l'étape :", + initialGeometry: + _toGeometryDTO(workingStep.geometry), + initialColor: null, + onSave: (geometry, color) { + setState(() { + workingStep.geometry = + _toEventGeometry(geometry); + }); + }, + ), + // Questions — uniquement en mode Escape Game + if (isEscapeMode) ...[ + Divider(height: 24), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Questions / Défis", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 15)), + IconButton( + icon: Icon(Icons.add_circle_outline, + color: kSuccess), + onPressed: () { + showNewOrUpdateQuizQuestion( + context, + null, + workingStep.id ?? "temp", + isEscapeMode, + (newQuestion) { + setState(() { + workingStep.quizQuestions = [ + ...(workingStep.quizQuestions ?? + []), + newQuestion + ]; + }); + }, + ); + }, + ), + ], + ), + if (workingStep.quizQuestions == null || + workingStep.quizQuestions!.isEmpty) + Padding( + padding: + const EdgeInsets.symmetric(vertical: 8), + child: Text( + "Aucune question configurée.", + style: TextStyle( + fontStyle: FontStyle.italic, + color: Colors.grey[600]), + ), + ) + else + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: workingStep.quizQuestions!.length, + itemBuilder: (context, qIndex) { + final question = + workingStep.quizQuestions![qIndex]; + return ListTile( + dense: true, + title: Text(question.label.isNotEmpty + ? question.label + .firstWhere( + (t) => t.language == 'FR', + orElse: () => + question.label[0]) + .value ?? + "Question $qIndex" + : "Question $qIndex"), + subtitle: Text( + "Type: ${question.validationQuestionType?.value == 2 ? 'Puzzle' : question.validationQuestionType?.value == 1 ? 'QCM' : 'Texte'}"), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(Icons.edit, + size: 18, color: kPrimaryColor), + onPressed: () { + showNewOrUpdateQuizQuestion( + context, + question, + workingStep.id ?? "temp", + isEscapeMode, + (updatedQuestion) { + setState(() { + workingStep.quizQuestions![ + qIndex] = updatedQuestion; + }); + }, + ); + }, + ), + IconButton( + icon: Icon(Icons.delete, + size: 18, color: kError), + onPressed: () { + showConfirmationDialog( + "Supprimer cette question ?", + () {}, + () => setState(() => workingStep + .quizQuestions! + .removeAt(qIndex)), + context, + ); + }, + ), + ], + ), + ); + }, + ), + ], + ], + ), + ), + ), + SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + height: 46, + child: RoundedButton( + text: "Annuler", + press: () => Navigator.pop(context), + color: kSecond, + fontSize: 15, + horizontal: 24, + ), + ), + SizedBox(width: 12), + SizedBox( + height: 46, + child: RoundedButton( + text: "Sauvegarder", + press: () { + onSave(workingStep); + Navigator.pop(context); + }, + color: kPrimaryColor, + fontSize: 15, + horizontal: 24, + ), + ), + ], + ), + ], + ), + ), + ); + }, + ); + }, + ); +} diff --git a/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateQuizQuestion.dart b/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateQuizQuestion.dart new file mode 100644 index 0000000..7ec5dee --- /dev/null +++ b/lib/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateQuizQuestion.dart @@ -0,0 +1,390 @@ +import 'package:flutter/material.dart'; +import 'package:manager_api_new/api.dart'; +import 'package:manager_app/constants.dart'; +import 'package:manager_app/Components/rounded_button.dart'; +import 'package:manager_app/Components/multi_string_input_container.dart'; +import 'package:manager_app/Components/resource_input_container.dart'; +import 'package:manager_app/Components/number_input_container.dart'; +import 'package:manager_app/Components/check_input_container.dart'; + +// Conversions between TranslationDTO and TranslationAndResourceDTO +// (ResponseDTO.label uses TranslationAndResourceDTO, but we use MultiStringInputContainer +// which requires TranslationDTO — the resource field is not needed for quiz responses) +List _toTranslationList( + List? list) => + (list ?? []) + .map((t) => TranslationDTO(language: t.language, value: t.value)) + .toList(); + +List _fromTranslationList( + List list) => + list + .map((t) => + TranslationAndResourceDTO(language: t.language, value: t.value)) + .toList(); + +// Creates an empty ResponseDTO; labels are populated by MultiStringInputContainer +ResponseDTO _emptyResponse({bool isGood = false, int order = 0}) => + ResponseDTO(label: [], isGood: isGood, order: order); + +void showNewOrUpdateQuizQuestion( + BuildContext context, + QuizQuestion? question, + String stepId, + bool isEscapeMode, + Function(QuizQuestion) onSave, +) { + // QuizQuestion.label is List — convert for display + List workingLabel = _toTranslationList( + question != null && question.label.isNotEmpty + ? question.label + : [TranslationAndResourceDTO(language: 'FR', value: '')]); + + List workingResponses = + question != null && question.responses.isNotEmpty + ? question.responses + .map((r) => ResponseDTO.fromJson(r.toJson())!) + .toList() + : []; + + QuizQuestion workingQuestion = QuizQuestion( + id: question?.id ?? 0, + label: _fromTranslationList(workingLabel), // kept in sync below + responses: workingResponses, + validationQuestionType: + question?.validationQuestionType ?? QuestionType.number0, + puzzleImageId: question?.puzzleImageId, + puzzleImage: question?.puzzleImage, + puzzleRows: question?.puzzleRows, + puzzleCols: question?.puzzleCols, + isSlidingPuzzle: question?.isSlidingPuzzle, + order: question?.order ?? 0, + guidedStepId: question?.guidedStepId ?? stepId, + ); + + showDialog( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (context, setState) { + final double screenWidth = MediaQuery.of(context).size.width; + final double screenHeight = MediaQuery.of(context).size.height; + final double dialogWidth = screenWidth * 0.65; + final double contentWidth = dialogWidth - 48; + final double halfWidth = (contentWidth - 20) / 2; + + void ensureSimpleResponse() { + if (workingQuestion.responses.isEmpty) { + workingQuestion.responses + .add(_emptyResponse(isGood: true, order: 0)); + } + workingQuestion.responses[0].isGood = true; + } + + return Dialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Container( + width: dialogWidth, + constraints: BoxConstraints(maxHeight: screenHeight * 0.85), + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + question == null + ? "Nouvelle Question" + : "Modifier la Question", + style: TextStyle( + color: kPrimaryColor, + fontSize: 20, + fontWeight: FontWeight.bold), + ), + SizedBox(height: 16), + Flexible( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // --- Intitulé (multi-langue via MultiStringInputContainer) --- + MultiStringInputContainer( + label: "Question posée :", + modalLabel: "Intitulé de la question", + initialValue: workingLabel, + onGetResult: (val) => setState(() { + workingLabel = val; + workingQuestion.label = _fromTranslationList(val); + }), + maxLines: 3, + isTitle: false, + ), + SizedBox(height: 16), + + // --- Type --- + Text("Type de validation :", + style: TextStyle(fontWeight: FontWeight.bold)), + SizedBox(height: 8), + DropdownButton( + value: workingQuestion.validationQuestionType, + items: [ + DropdownMenuItem( + value: QuestionType.number0, + child: Text("Simple (texte attendu)")), + DropdownMenuItem( + value: QuestionType.number1, + child: Text("Choix multiples (QCM)")), + DropdownMenuItem( + value: QuestionType.number2, + child: Text("Puzzle")), + ], + onChanged: (val) => setState(() { + workingQuestion.validationQuestionType = val; + workingQuestion.responses = []; + }), + ), + + // ========================================= + // Type 0 : Simple texte + // ========================================= + if (workingQuestion.validationQuestionType == + QuestionType.number0) ...[ + Divider(height: 24), + Text("Réponse attendue :", + style: TextStyle(fontWeight: FontWeight.bold)), + SizedBox(height: 8), + Builder(builder: (_) { + ensureSimpleResponse(); + final List respLabel = + _toTranslationList( + workingQuestion.responses[0].label); + return MultiStringInputContainer( + label: "", + modalLabel: "Réponse attendue", + initialValue: respLabel, + onGetResult: (val) => setState(() { + workingQuestion.responses[0].label = + _fromTranslationList(val); + workingQuestion.responses[0].isGood = true; + }), + maxLines: 1, + isTitle: true, + ); + }), + SizedBox(height: 4), + Text( + "La validation se fait par comparaison (insensible à la casse).", + style: TextStyle( + fontSize: 12, + fontStyle: FontStyle.italic, + color: Colors.grey[600]), + ), + ], + + // ========================================= + // Type 1 : QCM + // ========================================= + if (workingQuestion.validationQuestionType == + QuestionType.number1) ...[ + Divider(height: 24), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Réponses possibles :", + style: + TextStyle(fontWeight: FontWeight.bold)), + TextButton.icon( + icon: Icon(Icons.add_circle_outline, + color: kSuccess), + label: Text("Ajouter", + style: TextStyle(color: kSuccess)), + onPressed: () => setState(() => + workingQuestion.responses.add( + _emptyResponse( + order: workingQuestion + .responses.length))), + ), + ], + ), + if (workingQuestion.responses.isEmpty) + Padding( + padding: + const EdgeInsets.symmetric(vertical: 8), + child: Text( + "Aucune réponse définie. Ajoutez-en au moins une.", + style: TextStyle( + fontStyle: FontStyle.italic, + color: Colors.grey[600]), + ), + ) + else + Column( + children: List.generate( + workingQuestion.responses.length, (i) { + final resp = workingQuestion.responses[i]; + final List respLabel = + _toTranslationList(resp.label); + return Card( + margin: const EdgeInsets.only(bottom: 8), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + child: Row( + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + // Checkbox bonne réponse + Tooltip( + message: resp.isGood == true + ? "Bonne réponse ✓" + : "Mauvaise réponse", + child: Checkbox( + value: resp.isGood ?? false, + activeColor: kSuccess, + onChanged: (val) => setState( + () => resp.isGood = val), + ), + ), + // Traductions (via MultiStringInputContainer) + Expanded( + child: MultiStringInputContainer( + label: "Réponse ${i + 1} :", + modalLabel: "Réponse ${i + 1}", + initialValue: respLabel, + onGetResult: (val) => setState( + () => resp.label = + _fromTranslationList( + val)), + maxLines: 1, + isTitle: true, + ), + ), + // Supprimer + IconButton( + icon: Icon(Icons.delete_outline, + color: kError, size: 20), + onPressed: () => setState(() => + workingQuestion.responses + .removeAt(i)), + ), + ], + ), + ), + ); + }), + ), + SizedBox(height: 4), + Text( + "✓ = bonne réponse. Plusieurs peuvent être correctes.", + style: TextStyle( + fontSize: 12, + fontStyle: FontStyle.italic, + color: Colors.grey[600]), + ), + ], + + // ========================================= + // Type 2 : Puzzle + // ========================================= + if (workingQuestion.validationQuestionType == + QuestionType.number2) ...[ + Divider(height: 24), + Text("Configuration du Puzzle", + style: TextStyle(fontWeight: FontWeight.bold)), + SizedBox(height: 12), + ResourceInputContainer( + label: "Image du puzzle :", + initialValue: workingQuestion.puzzleImageId, + onChanged: (res) => setState(() { + workingQuestion.puzzleImageId = res.id; + workingQuestion.puzzleImage = + Resource.fromJson(res.toJson()); + }), + ), + SizedBox(height: 12), + Row( + children: [ + SizedBox( + width: halfWidth, + child: NumberInputContainer( + label: "Lignes :", + initialValue: + workingQuestion.puzzleRows ?? 3, + onChanged: (val) => setState(() => + workingQuestion.puzzleRows = + int.tryParse(val) ?? 3), + isSmall: true, + ), + ), + SizedBox(width: 20), + SizedBox( + width: halfWidth, + child: NumberInputContainer( + label: "Colonnes :", + initialValue: + workingQuestion.puzzleCols ?? 3, + onChanged: (val) => setState(() => + workingQuestion.puzzleCols = + int.tryParse(val) ?? 3), + isSmall: true, + ), + ), + ], + ), + SizedBox(height: 8), + CheckInputContainer( + label: "Puzzle glissant (Sliding) :", + isChecked: + workingQuestion.isSlidingPuzzle ?? false, + onChanged: (val) => setState( + () => workingQuestion.isSlidingPuzzle = val), + ), + ], + ], + ), + ), + ), + SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + height: 46, + child: RoundedButton( + text: "Annuler", + press: () => Navigator.pop(context), + color: kSecond, + fontSize: 15, + horizontal: 24, + ), + ), + SizedBox(width: 12), + SizedBox( + height: 46, + child: RoundedButton( + text: "Sauvegarder", + press: () { + for (int i = 0; + i < workingQuestion.responses.length; + i++) { + workingQuestion.responses[i].order = i; + } + onSave(workingQuestion); + Navigator.pop(context); + }, + color: kPrimaryColor, + fontSize: 15, + horizontal: 24, + ), + ), + ], + ), + ], + ), + ), + ); + }, + ); + }, + ); +} diff --git a/lib/Screens/Configurations/Section/section_detail_screen.dart b/lib/Screens/Configurations/Section/section_detail_screen.dart index 5611174..d5bf6b7 100644 --- a/lib/Screens/Configurations/Section/section_detail_screen.dart +++ b/lib/Screens/Configurations/Section/section_detail_screen.dart @@ -39,6 +39,7 @@ import 'package:pasteboard/pasteboard.dart'; import 'dart:html' as html; import 'SubSection/Weather/weather_config.dart'; +import 'SubSection/Event/event_config.dart'; class SectionDetailScreen extends StatefulWidget { final String id; @@ -61,18 +62,20 @@ class _SectionDetailScreenState extends State { Object? rawSectionData; return FutureBuilder( - future: getSection(widget.id, (appContext.getContext() as ManagerAppContext).clientAPI!), + future: getSection(widget.id, + (appContext.getContext() as ManagerAppContext).clientAPI!), builder: (context, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done) { rawSectionData = snapshot.data; var nullableSection = SectionDTO.fromJson(rawSectionData); - if(nullableSection != null) { + if (nullableSection != null) { sectionDTO = nullableSection; return Stack( children: [ - bodySection(rawSectionData, size, appContext, context, globalKey), + bodySection( + rawSectionData, size, appContext, context, globalKey), Align( alignment: AlignmentDirectional.bottomCenter, child: Container( @@ -83,23 +86,22 @@ class _SectionDetailScreenState extends State { ], ); } else { - return Center(child: Text("Une erreur est survenue lors de la récupération de la section")); + return Center( + child: Text( + "Une erreur est survenue lors de la récupération de la section")); } } else if (snapshot.connectionState == ConnectionState.none) { return Text("No data"); } else { return Center( child: Container( - height: size.height * 0.2, - child: CommonLoader() - ) - ); + height: size.height * 0.2, child: CommonLoader())); } - } - ); + }); } - Widget bodySection(Object? rawSectionDTO, Size size, AppContext appContext, BuildContext context, GlobalKey globalKey) { + Widget bodySection(Object? rawSectionDTO, Size size, AppContext appContext, + BuildContext context, GlobalKey globalKey) { ManagerAppContext managerAppContext = appContext.getContext(); //SectionDTO? sectionDTO = SectionDTO.fromJson(rawSectionDTO); @@ -131,37 +133,42 @@ class _SectionDetailScreenState extends State { size: 25, ), ), - Text(sectionDTO.label!, style: TextStyle(fontSize: 30, fontWeight: FontWeight.w400)), + Text(sectionDTO.label!, + style: TextStyle( + fontSize: 30, + fontWeight: FontWeight.w400)), //if((appContext.getContext() as ManagerAppContext).selectedConfiguration!.isMobile!) - DownloadPDF(sections: [sectionDTO]), + DownloadPDF(sections: [sectionDTO]), ], ), Padding( padding: const EdgeInsets.all(5.0), - child: Text(DateFormat('dd/MM/yyyy').format(sectionDTO.dateCreation!), style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200)), + child: Text( + DateFormat('dd/MM/yyyy') + .format(sectionDTO.dateCreation!), + style: TextStyle( + fontSize: 15, fontWeight: FontWeight.w200)), ), ], ), - ) - ), + )), Padding( padding: const EdgeInsets.all(5.0), child: Align( alignment: AlignmentDirectional.centerEnd, child: InkWell( onTap: () { - ManagerAppContext managerAppContext = appContext.getContext(); + ManagerAppContext managerAppContext = + appContext.getContext(); managerAppContext.selectedSection = null; appContext.setContext(managerAppContext); }, child: Container( child: Icon( - Icons.arrow_back, - color: kPrimaryColor, - size: 50.0, - ) - ) - ), + Icons.arrow_back, + color: kPrimaryColor, + size: 50.0, + ))), ), ) ], @@ -181,62 +188,81 @@ class _SectionDetailScreenState extends State { mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ //if((appContext.getContext() as ManagerAppContext).selectedConfiguration!.isMobile!) - Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - InkWell( - onTap: () async { - var image = await _captureAndSharePng(globalKey, sectionDTO.id!); - await readAndWriteFiles(image); - showNotification(kSuccess, kWhite, 'Ce QR code a été copié dans le presse papier', context, null); - }, - child: Container( - width: size.width *0.1, - height: 125, - child: RepaintBoundary( - key: globalKey, - child: QrImageView( - padding: EdgeInsets.only(left: 5.0, top: 5.0, bottom: 5.0, right: 5.0), - data: "${managerAppContext.host!.replaceFirst("api", "web")}/${managerAppContext.instanceId}/${managerAppContext.selectedConfiguration!.id}/${sectionDTO.id!}", - version: QrVersions.auto, - size: 50.0, - ), + Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + InkWell( + onTap: () async { + var image = await _captureAndSharePng( + globalKey, sectionDTO.id!); + await readAndWriteFiles(image); + showNotification( + kSuccess, + kWhite, + 'Ce QR code a été copié dans le presse papier', + context, + null); + }, + child: Container( + width: size.width * 0.1, + height: 125, + child: RepaintBoundary( + key: globalKey, + child: QrImageView( + padding: EdgeInsets.only( + left: 5.0, + top: 5.0, + bottom: 5.0, + right: 5.0), + data: + "${managerAppContext.host!.replaceFirst("api", "web")}/${managerAppContext.instanceId}/${managerAppContext.selectedConfiguration!.id}/${sectionDTO.id!}", + version: QrVersions.auto, + size: 50.0, ), ), ), - SelectableText(sectionDTO.id!, style: new TextStyle(fontSize: 15)) - ], - ), - CheckInputContainer( - label: "Beacon :", - isChecked: sectionDTO.isBeacon!, + ), + SelectableText(sectionDTO.id!, + style: new TextStyle(fontSize: 15)) + ], + ), + CheckInputContainer( + label: "Beacon :", + isChecked: sectionDTO.isBeacon!, + onChanged: (value) { + setState(() { + sectionDTO.isBeacon = value; + save(false, appContext); + }); + }, + ), + if (sectionDTO.isBeacon!) + NumberInputContainer( + label: "Identifiant Beacon :", + initialValue: sectionDTO.beaconId != null + ? sectionDTO.beaconId! + : 0, + isSmall: true, onChanged: (value) { - setState(() { - sectionDTO.isBeacon = value; - save(false, appContext); - }); + try { + sectionDTO.beaconId = int.parse(value); + } catch (e) { + print('BeaconId not a number'); + showNotification( + Colors.orange, + kWhite, + 'Cela doit être un chiffre', + context, + null); + } }, ), - if(sectionDTO.isBeacon!) - NumberInputContainer( - label: "Identifiant Beacon :", - initialValue: sectionDTO.beaconId != null ? sectionDTO.beaconId! : 0, - isSmall: true, - onChanged: (value) { - try { - sectionDTO.beaconId = int.parse(value); - } catch (e) { - print('BeaconId not a number'); - showNotification(Colors.orange, kWhite, 'Cela doit être un chiffre', context, null); - } - }, - ), - ], - ), + ], + ), Column( mainAxisAlignment: MainAxisAlignment.spaceAround, crossAxisAlignment: CrossAxisAlignment.start, @@ -297,7 +323,7 @@ class _SectionDetailScreenState extends State { initialValue: sectionDTO.imageId, color: kPrimaryColor, onChanged: (ResourceDTO resource) { - if(resource.id == null) { + if (resource.id == null) { sectionDTO.imageId = null; sectionDTO.imageSource = null; } else { @@ -315,7 +341,7 @@ class _SectionDetailScreenState extends State { ), ), ), - ),// FIELDS SECTION + ), // FIELDS SECTION Container( //width: size.width * 0.8, height: size.height * 0.5, @@ -326,8 +352,7 @@ class _SectionDetailScreenState extends State { decoration: BoxDecoration( //color: Colors.lightGreen, borderRadius: BorderRadius.circular(30), - border: Border.all(width: 1.5, color: kSecond) - ), + border: Border.all(width: 1.5, color: kSecond)), ), ], ), @@ -386,10 +411,12 @@ class _SectionDetailScreenState extends State { Future cancel(AppContext appContext) async { ManagerAppContext managerAppContext = appContext.getContext(); - Object? rawData = await (appContext.getContext() as ManagerAppContext).clientAPI!.sectionApi!.sectionGetDetail(sectionDTO.id!); + Object? rawData = await (appContext.getContext() as ManagerAppContext) + .clientAPI! + .sectionApi! + .sectionGetDetail(sectionDTO.id!); var nullableSection = SectionDTO.fromJson(rawData); - if(nullableSection != null) - { + if (nullableSection != null) { managerAppContext.selectedSection = nullableSection!; appContext.setContext(managerAppContext); } @@ -397,22 +424,22 @@ class _SectionDetailScreenState extends State { Future delete(AppContext appContext) async { showConfirmationDialog( - "Êtes-vous sûr de vouloir supprimer cette section ?", - () {}, - () async { - ManagerAppContext managerAppContext = appContext.getContext(); - await managerAppContext.clientAPI!.sectionApi!.sectionDelete(sectionDTO.id!); - managerAppContext.selectedSection = null; - appContext.setContext(managerAppContext); - }, - context - ); + "Êtes-vous sûr de vouloir supprimer cette section ?", () {}, () async { + ManagerAppContext managerAppContext = appContext.getContext(); + await managerAppContext.clientAPI!.sectionApi! + .sectionDelete(sectionDTO.id!); + managerAppContext.selectedSection = null; + appContext.setContext(managerAppContext); + }, context); } Future save(bool isTraduction, AppContext appContext) async { updateSectionDetail(); - var sectionResult = await (appContext.getContext() as ManagerAppContext).clientAPI!.sectionApi!.sectionUpdate(sectionDetailDTO); + var sectionResult = await (appContext.getContext() as ManagerAppContext) + .clientAPI! + .sectionApi! + .sectionUpdate(sectionDetailDTO); SectionDTO? section = SectionDTO.fromJson(sectionResult); ManagerAppContext managerAppContext = appContext.getContext(); @@ -420,14 +447,20 @@ class _SectionDetailScreenState extends State { appContext.setContext(managerAppContext); if (isTraduction) { - showNotification(kSuccess, kWhite, 'Les traductions de la section ont été sauvegardées avec succès', context, null); + showNotification( + kSuccess, + kWhite, + 'Les traductions de la section ont été sauvegardées avec succès', + context, + null); } else { - showNotification(kSuccess, kWhite, 'La section a été sauvegardée avec succès', context, null); + showNotification(kSuccess, kWhite, + 'La section a été sauvegardée avec succès', context, null); } } getSpecificData(Object? rawSectionData, AppContext appContext) { - switch(sectionDTO.type) { + switch (sectionDTO.type) { case SectionType.Map: MapDTO mapDTO = MapDTO.fromJson(rawSectionData)!; sectionDetailDTO = mapDTO; @@ -529,19 +562,28 @@ class _SectionDetailScreenState extends State { sectionDetailDTO = updatedWeather; }, ); + case SectionType.Event: + SectionEventDTO eventDTO = SectionEventDTO.fromJson(rawSectionData)!; + sectionDetailDTO = eventDTO; + return EventConfig( + initialValue: eventDTO, + onChanged: (SectionEventDTO updatedEvent) { + sectionDetailDTO = updatedEvent; + }, + ); } } updateSectionDetail() { - switch(sectionDTO.type) - { + switch (sectionDTO.type) { case SectionType.Map: (sectionDetailDTO as MapDTO).id = sectionDTO.id; (sectionDetailDTO as MapDTO).order = sectionDTO.order; (sectionDetailDTO as MapDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as MapDTO).type = sectionDTO.type; (sectionDetailDTO as MapDTO).instanceId = sectionDTO.instanceId; - (sectionDetailDTO as MapDTO).configurationId = sectionDTO.configurationId; + (sectionDetailDTO as MapDTO).configurationId = + sectionDTO.configurationId; (sectionDetailDTO as MapDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as MapDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as MapDTO).label = sectionDTO.label; @@ -561,7 +603,8 @@ class _SectionDetailScreenState extends State { (sectionDetailDTO as SliderDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as SliderDTO).type = sectionDTO.type; (sectionDetailDTO as SliderDTO).instanceId = sectionDTO.instanceId; - (sectionDetailDTO as SliderDTO).configurationId = sectionDTO.configurationId; + (sectionDetailDTO as SliderDTO).configurationId = + sectionDTO.configurationId; (sectionDetailDTO as SliderDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as SliderDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as SliderDTO).label = sectionDTO.label; @@ -581,7 +624,8 @@ class _SectionDetailScreenState extends State { (sectionDetailDTO as VideoDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as VideoDTO).type = sectionDTO.type; (sectionDetailDTO as VideoDTO).instanceId = sectionDTO.instanceId; - (sectionDetailDTO as VideoDTO).configurationId = sectionDTO.configurationId; + (sectionDetailDTO as VideoDTO).configurationId = + sectionDTO.configurationId; (sectionDetailDTO as VideoDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as VideoDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as VideoDTO).label = sectionDTO.label; @@ -601,7 +645,8 @@ class _SectionDetailScreenState extends State { (sectionDetailDTO as WebDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as WebDTO).type = sectionDTO.type; (sectionDetailDTO as WebDTO).instanceId = sectionDTO.instanceId; - (sectionDetailDTO as WebDTO).configurationId = sectionDTO.configurationId; + (sectionDetailDTO as WebDTO).configurationId = + sectionDTO.configurationId; (sectionDetailDTO as WebDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as WebDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as WebDTO).label = sectionDTO.label; @@ -621,7 +666,8 @@ class _SectionDetailScreenState extends State { (sectionDetailDTO as MenuDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as MenuDTO).type = sectionDTO.type; (sectionDetailDTO as MenuDTO).instanceId = sectionDTO.instanceId; - (sectionDetailDTO as MenuDTO).configurationId = sectionDTO.configurationId; + (sectionDetailDTO as MenuDTO).configurationId = + sectionDTO.configurationId; (sectionDetailDTO as MenuDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as MenuDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as MenuDTO).label = sectionDTO.label; @@ -641,7 +687,8 @@ class _SectionDetailScreenState extends State { (sectionDetailDTO as QuizDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as QuizDTO).type = sectionDTO.type; (sectionDetailDTO as QuizDTO).instanceId = sectionDTO.instanceId; - (sectionDetailDTO as QuizDTO).configurationId = sectionDTO.configurationId; + (sectionDetailDTO as QuizDTO).configurationId = + sectionDTO.configurationId; (sectionDetailDTO as QuizDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as QuizDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as QuizDTO).label = sectionDTO.label; @@ -661,7 +708,8 @@ class _SectionDetailScreenState extends State { (sectionDetailDTO as ArticleDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as ArticleDTO).type = sectionDTO.type; (sectionDetailDTO as ArticleDTO).instanceId = sectionDTO.instanceId; - (sectionDetailDTO as ArticleDTO).configurationId = sectionDTO.configurationId; + (sectionDetailDTO as ArticleDTO).configurationId = + sectionDTO.configurationId; (sectionDetailDTO as ArticleDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as ArticleDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as ArticleDTO).label = sectionDTO.label; @@ -681,7 +729,8 @@ class _SectionDetailScreenState extends State { (sectionDetailDTO as PdfDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as PdfDTO).type = sectionDTO.type; (sectionDetailDTO as PdfDTO).instanceId = sectionDTO.instanceId; - (sectionDetailDTO as PdfDTO).configurationId = sectionDTO.configurationId; + (sectionDetailDTO as PdfDTO).configurationId = + sectionDTO.configurationId; (sectionDetailDTO as PdfDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as PdfDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as PdfDTO).label = sectionDTO.label; @@ -701,7 +750,8 @@ class _SectionDetailScreenState extends State { (sectionDetailDTO as GameDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as GameDTO).type = sectionDTO.type; (sectionDetailDTO as GameDTO).instanceId = sectionDTO.instanceId; - (sectionDetailDTO as GameDTO).configurationId = sectionDTO.configurationId; + (sectionDetailDTO as GameDTO).configurationId = + sectionDTO.configurationId; (sectionDetailDTO as GameDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as GameDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as GameDTO).label = sectionDTO.label; @@ -721,7 +771,8 @@ class _SectionDetailScreenState extends State { (sectionDetailDTO as AgendaDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as AgendaDTO).type = sectionDTO.type; (sectionDetailDTO as AgendaDTO).instanceId = sectionDTO.instanceId; - (sectionDetailDTO as AgendaDTO).configurationId = sectionDTO.configurationId; + (sectionDetailDTO as AgendaDTO).configurationId = + sectionDTO.configurationId; (sectionDetailDTO as AgendaDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as AgendaDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as AgendaDTO).label = sectionDTO.label; @@ -741,7 +792,8 @@ class _SectionDetailScreenState extends State { (sectionDetailDTO as WeatherDTO).dateCreation = sectionDTO.dateCreation; (sectionDetailDTO as WeatherDTO).type = sectionDTO.type; (sectionDetailDTO as WeatherDTO).instanceId = sectionDTO.instanceId; - (sectionDetailDTO as WeatherDTO).configurationId = sectionDTO.configurationId; + (sectionDetailDTO as WeatherDTO).configurationId = + sectionDTO.configurationId; (sectionDetailDTO as WeatherDTO).isSubSection = sectionDTO.isSubSection; (sectionDetailDTO as WeatherDTO).parentId = sectionDTO.parentId; (sectionDetailDTO as WeatherDTO).label = sectionDTO.label; @@ -755,24 +807,52 @@ class _SectionDetailScreenState extends State { (sectionDetailDTO as WeatherDTO).longitude = sectionDTO.longitude; (sectionDetailDTO as WeatherDTO).meterZoneGPS = sectionDTO.meterZoneGPS; break; + case SectionType.Event: + (sectionDetailDTO as SectionEventDTO).id = sectionDTO.id; + (sectionDetailDTO as SectionEventDTO).order = sectionDTO.order; + (sectionDetailDTO as SectionEventDTO).dateCreation = + sectionDTO.dateCreation; + (sectionDetailDTO as SectionEventDTO).type = sectionDTO.type; + (sectionDetailDTO as SectionEventDTO).instanceId = + sectionDTO.instanceId; + (sectionDetailDTO as SectionEventDTO).configurationId = + sectionDTO.configurationId; + (sectionDetailDTO as SectionEventDTO).isSubSection = + sectionDTO.isSubSection; + (sectionDetailDTO as SectionEventDTO).parentId = sectionDTO.parentId; + (sectionDetailDTO as SectionEventDTO).label = sectionDTO.label; + (sectionDetailDTO as SectionEventDTO).title = sectionDTO.title; + (sectionDetailDTO as SectionEventDTO).description = + sectionDTO.description; + (sectionDetailDTO as SectionEventDTO).imageId = sectionDTO.imageId; + (sectionDetailDTO as SectionEventDTO).imageSource = + sectionDTO.imageSource; + (sectionDetailDTO as SectionEventDTO).isBeacon = sectionDTO.isBeacon; + (sectionDetailDTO as SectionEventDTO).beaconId = sectionDTO.beaconId; + (sectionDetailDTO as SectionEventDTO).latitude = sectionDTO.latitude; + (sectionDetailDTO as SectionEventDTO).longitude = sectionDTO.longitude; + (sectionDetailDTO as SectionEventDTO).meterZoneGPS = + sectionDTO.meterZoneGPS; + break; } } } Future getSection(String sectionId, Client client) async { - try{ + try { Object? section = await client.sectionApi!.sectionGetDetail(sectionId); return section; - - } catch(e) { + } catch (e) { print(e); return null; } } -Future _captureAndSharePng(GlobalKey globalKey, String sectionId) async { +Future _captureAndSharePng( + GlobalKey globalKey, String sectionId) async { try { - RenderRepaintBoundary ? boundary = globalKey.currentContext!.findRenderObject()! as RenderRepaintBoundary; + RenderRepaintBoundary? boundary = + globalKey.currentContext!.findRenderObject()! as RenderRepaintBoundary; var image = await boundary.toImage(); ByteData? byteData = await image.toByteData(format: ImageByteFormat.png); Uint8List pngBytes = byteData!.buffer.asUint8List(); @@ -782,8 +862,7 @@ Future _captureAndSharePng(GlobalKey globalKey, String sectionId) as a.click(); return pngBytes; - - } catch(e) { + } catch (e) { print(e.toString()); return null; } diff --git a/lib/Screens/Configurations/new_section_popup.dart b/lib/Screens/Configurations/new_section_popup.dart index b103d76..5a2c313 100644 --- a/lib/Screens/Configurations/new_section_popup.dart +++ b/lib/Screens/Configurations/new_section_popup.dart @@ -8,50 +8,64 @@ import 'package:manager_app/app_context.dart'; import 'package:manager_app/constants.dart'; import 'package:manager_api_new/api.dart'; -Future showNewSection(String configurationId, AppContext appContext, BuildContext contextBuild, bool isSubSection) { +Future showNewSection(String configurationId, + AppContext appContext, BuildContext contextBuild, bool isSubSection) { SectionDTO sectionDTO = new SectionDTO(); sectionDTO.label = ""; sectionDTO.configurationId = configurationId; sectionDTO.isSubSection = isSubSection; - sectionDTO.parentId = isSubSection ? (appContext.getContext() as ManagerAppContext).selectedSection!.id : null; + sectionDTO.isActive = true; + sectionDTO.parentId = isSubSection + ? (appContext.getContext() as ManagerAppContext).selectedSection!.id + : null; Size size = MediaQuery.of(contextBuild).size; sectionDTO.type = SectionType.Map; var section = showDialog( builder: (BuildContext context) => AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(20.0)) - ), - content: SingleChildScrollView( - child: SizedBox( - width: 750, - height: 250, - child: Container( - constraints: BoxConstraints(minHeight: 300, minWidth: 300), - child: Column( - children: [ - Text(isSubSection? "Nouvelle sous section": "Nouvelle section", style: new TextStyle(fontSize: 25, fontWeight: FontWeight.w400)), - Column( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20.0))), + content: SingleChildScrollView( + child: SizedBox( + width: 750, + height: 250, + child: Container( + constraints: BoxConstraints(minHeight: 300, minWidth: 300), + child: Column( children: [ - SizedBox( - height: 100, - child: StringInputContainer( - label: "Nom :", - initialValue: sectionDTO.label, - onChanged: (value) { - sectionDTO.label = value; - }, - ), - ), - DropDownInputContainer( - label: "Type:", - values: isSubSection ? section_types.where((sectionType) => sectionType != "Menu").toList(): section_types.toList(), // Todo get menu by enum type - initialValue: "Map", - onChange: (String? value) { - sectionDTO.type = SectionType.fromJson(value); - }, - ), - /*MultiSelectContainer( + Text( + isSubSection + ? "Nouvelle sous section" + : "Nouvelle section", + style: new TextStyle( + fontSize: 25, fontWeight: FontWeight.w400)), + Column( + children: [ + SizedBox( + height: 100, + child: StringInputContainer( + label: "Nom :", + initialValue: sectionDTO.label, + onChanged: (value) { + sectionDTO.label = value; + }, + ), + ), + DropDownInputContainer( + label: "Type:", + values: isSubSection + ? section_types + .where( + (sectionType) => sectionType != "Menu") + .toList() + : section_types + .toList(), // Todo get menu by enum type + initialValue: "Map", + onChange: (String? value) { + sectionDTO.type = SectionType.fromJson(value); + }, + ), + /*MultiSelectContainer( label: "Type:", initialValue: isMobile ? ["Article"] : ["Map"], isMultiple: false, @@ -61,72 +75,80 @@ Future showNewSection(String configurationId, AppContext appContext sectionDTO.type = SectionType.fromJson(tempOutput[0]); }, ),*/ + ], + ), ], ), + ), + ), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Align( + alignment: AlignmentDirectional.bottomEnd, + child: Container( + width: 175, + height: 70, + child: RoundedButton( + text: "Annuler", + icon: Icons.undo, + color: kSecond, + press: () { + Navigator.of(context).pop(); + }, + fontSize: 20, + ), + ), + ), + Align( + alignment: AlignmentDirectional.bottomEnd, + child: Container( + width: 150, + height: 70, + child: RoundedButton( + text: "Créer", + icon: Icons.check, + color: kPrimaryColor, + textColor: kWhite, + press: () { + if (sectionDTO.label != null && + sectionDTO.label!.length > 2) { + //onYes(); + Navigator.of(context).pop(sectionDTO); + //create(sectionDTO, appContext, context, isSubSection, sendSubSection); + } else { + showNotification( + Colors.orange, + kWhite, + 'Veuillez spécifier un nom pour la nouvelle section', + context, + null); + } + }, + fontSize: 20, + ), + ), + ), ], ), - ), - ), - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Align( - alignment: AlignmentDirectional.bottomEnd, - child: Container( - width: 175, - height: 70, - child: RoundedButton( - text: "Annuler", - icon: Icons.undo, - color: kSecond, - press: () { - Navigator.of(context).pop(); - }, - fontSize: 20, - ), - ), - ), - Align( - alignment: AlignmentDirectional.bottomEnd, - child: Container( - width: 150, - height: 70, - child: RoundedButton( - text: "Créer", - icon: Icons.check, - color: kPrimaryColor, - textColor: kWhite, - press: () { - if(sectionDTO.label != null && sectionDTO.label!.length > 2) { - //onYes(); - Navigator.of(context).pop(sectionDTO); - //create(sectionDTO, appContext, context, isSubSection, sendSubSection); - } else { - showNotification(Colors.orange, kWhite, 'Veuillez spécifier un nom pour la nouvelle section', context, null); - } - }, - fontSize: 20, - ), - ), - ), ], ), - ], - ), context: contextBuild - ); + context: contextBuild); return section; } -void create(SectionDTO sectionDTO, AppContext appContext, BuildContext context, bool isSubSection, Function? sendSubSection) async { +void create(SectionDTO sectionDTO, AppContext appContext, BuildContext context, + bool isSubSection, Function? sendSubSection) async { if (sectionDTO.label != null) { ManagerAppContext managerAppContext = appContext.getContext(); sectionDTO.instanceId = managerAppContext.instanceId; sectionDTO.isBeacon = false; sectionDTO.dateCreation = DateTime.now(); - SectionDTO? newSection = await managerAppContext.clientAPI!.sectionApi!.sectionCreate(sectionDTO); + SectionDTO? newSection = await managerAppContext.clientAPI!.sectionApi! + .sectionCreate(sectionDTO); if (!isSubSection) { /*if (managerAppContext.selectedConfiguration.sectionIds == null) { @@ -134,10 +156,11 @@ void create(SectionDTO sectionDTO, AppContext appContext, BuildContext context, } managerAppContext.selectedConfiguration.sectionIds.add(newSection.id);*/ appContext.setContext(managerAppContext); - showNotification(kSuccess, kWhite, 'La section a été créée avec succès !', context, null); + showNotification(kSuccess, kWhite, 'La section a été créée avec succès !', + context, null); } else { sendSubSection!(newSection); } Navigator.of(context).pop(); } -} \ No newline at end of file +} diff --git a/manager_api_new/lib/model/game_dto.dart b/manager_api_new/lib/model/game_dto.dart index af4780f..e1bf2f2 100644 --- a/manager_api_new/lib/model/game_dto.dart +++ b/manager_api_new/lib/model/game_dto.dart @@ -39,6 +39,7 @@ class GameDTO { this.rows, this.cols, this.gameType, + this.guidedPaths = const [], }); String? id; @@ -135,6 +136,8 @@ class GameDTO { /// GameTypes? gameType; + List? guidedPaths; + @override bool operator ==(Object other) => identical(this, other) || @@ -164,7 +167,8 @@ class GameDTO { other.puzzleImageId == puzzleImageId && other.rows == rows && other.cols == cols && - other.gameType == gameType; + other.gameType == gameType && + _deepEquality.equals(other.guidedPaths, guidedPaths); @override int get hashCode => @@ -194,11 +198,12 @@ class GameDTO { (puzzleImageId == null ? 0 : puzzleImageId!.hashCode) + (rows == null ? 0 : rows!.hashCode) + (cols == null ? 0 : cols!.hashCode) + - (gameType == null ? 0 : gameType!.hashCode); + (gameType == null ? 0 : gameType!.hashCode) + + (guidedPaths == null ? 0 : guidedPaths!.hashCode); @override String toString() => - 'GameDTO[id=$id, label=$label, title=$title, description=$description, isActive=$isActive, imageId=$imageId, imageSource=$imageSource, configurationId=$configurationId, isSubSection=$isSubSection, parentId=$parentId, type=$type, dateCreation=$dateCreation, order=$order, instanceId=$instanceId, latitude=$latitude, longitude=$longitude, meterZoneGPS=$meterZoneGPS, isBeacon=$isBeacon, beaconId=$beaconId, messageDebut=$messageDebut, messageFin=$messageFin, puzzleImage=$puzzleImage, puzzleImageId=$puzzleImageId, rows=$rows, cols=$cols, gameType=$gameType]'; + 'GameDTO[id=$id, label=$label, title=$title, description=$description, isActive=$isActive, imageId=$imageId, imageSource=$imageSource, configurationId=$configurationId, isSubSection=$isSubSection, parentId=$parentId, type=$type, dateCreation=$dateCreation, order=$order, instanceId=$instanceId, latitude=$latitude, longitude=$longitude, meterZoneGPS=$meterZoneGPS, isBeacon=$isBeacon, beaconId=$beaconId, messageDebut=$messageDebut, messageFin=$messageFin, puzzleImage=$puzzleImage, puzzleImageId=$puzzleImageId, rows=$rows, cols=$cols, gameType=$gameType, guidedPaths=$guidedPaths]'; Map toJson() { final json = {}; @@ -213,12 +218,12 @@ class GameDTO { json[r'label'] = null; } if (this.title != null) { - json[r'title'] = this.title; + json[r'title'] = this.title!.map((v) => v.toJson()).toList(); } else { json[r'title'] = null; } if (this.description != null) { - json[r'description'] = this.description; + json[r'description'] = this.description!.map((v) => v.toJson()).toList(); } else { json[r'description'] = null; } @@ -298,12 +303,13 @@ class GameDTO { json[r'beaconId'] = null; } if (this.messageDebut != null) { - json[r'messageDebut'] = this.messageDebut; + json[r'messageDebut'] = + this.messageDebut!.map((v) => v.toJson()).toList(); } else { json[r'messageDebut'] = null; } if (this.messageFin != null) { - json[r'messageFin'] = this.messageFin; + json[r'messageFin'] = this.messageFin!.map((v) => v.toJson()).toList(); } else { json[r'messageFin'] = null; } @@ -332,6 +338,11 @@ class GameDTO { } else { json[r'gameType'] = null; } + if (this.guidedPaths != null) { + json[r'guidedPaths'] = this.guidedPaths!.map((v) => v.toJson()).toList(); + } else { + json[r'guidedPaths'] = null; + } return json; } @@ -383,6 +394,7 @@ class GameDTO { rows: mapValueOfType(json, r'rows'), cols: mapValueOfType(json, r'cols'), gameType: GameTypes.fromJson(json[r'gameType']), + guidedPaths: GuidedPathDTO.listFromJson(json[r'guidedPaths']), ); } return null; diff --git a/manager_api_new/lib/model/guided_path_dto.dart b/manager_api_new/lib/model/guided_path_dto.dart index e2b1cf1..45e8bcd 100644 --- a/manager_api_new/lib/model/guided_path_dto.dart +++ b/manager_api_new/lib/model/guided_path_dto.dart @@ -19,6 +19,7 @@ class GuidedPathDTO { this.description = const [], this.sectionMapId, this.sectionEventId, + this.sectionGameId, this.isLinear, this.requireSuccessToAdvance, this.hideNextStepsUntilComplete, @@ -38,6 +39,8 @@ class GuidedPathDTO { String? sectionEventId; + String? sectionGameId; + /// /// Please note: This property should have been non-nullable! Since the specification file /// does not include a default value (using the "default:" property), however, the generated @@ -82,6 +85,7 @@ class GuidedPathDTO { _deepEquality.equals(other.description, description) && other.sectionMapId == sectionMapId && other.sectionEventId == sectionEventId && + other.sectionGameId == sectionGameId && other.isLinear == isLinear && other.requireSuccessToAdvance == requireSuccessToAdvance && other.hideNextStepsUntilComplete == hideNextStepsUntilComplete && @@ -97,6 +101,7 @@ class GuidedPathDTO { (description == null ? 0 : description!.hashCode) + (sectionMapId == null ? 0 : sectionMapId!.hashCode) + (sectionEventId == null ? 0 : sectionEventId!.hashCode) + + (sectionGameId == null ? 0 : sectionGameId!.hashCode) + (isLinear == null ? 0 : isLinear!.hashCode) + (requireSuccessToAdvance == null ? 0 @@ -109,7 +114,7 @@ class GuidedPathDTO { @override String toString() => - 'GuidedPathDTO[id=$id, instanceId=$instanceId, title=$title, description=$description, sectionMapId=$sectionMapId, sectionEventId=$sectionEventId, isLinear=$isLinear, requireSuccessToAdvance=$requireSuccessToAdvance, hideNextStepsUntilComplete=$hideNextStepsUntilComplete, order=$order, steps=$steps]'; + 'GuidedPathDTO[id=$id, instanceId=$instanceId, title=$title, description=$description, sectionMapId=$sectionMapId, sectionEventId=$sectionEventId, sectionGameId=$sectionGameId, isLinear=$isLinear, requireSuccessToAdvance=$requireSuccessToAdvance, hideNextStepsUntilComplete=$hideNextStepsUntilComplete, order=$order, steps=$steps]'; Map toJson() { final json = {}; @@ -124,12 +129,12 @@ class GuidedPathDTO { json[r'instanceId'] = null; } if (this.title != null) { - json[r'title'] = this.title; + json[r'title'] = this.title!.map((v) => v.toJson()).toList(); } else { json[r'title'] = null; } if (this.description != null) { - json[r'description'] = this.description; + json[r'description'] = this.description!.map((v) => v.toJson()).toList(); } else { json[r'description'] = null; } @@ -143,6 +148,11 @@ class GuidedPathDTO { } else { json[r'sectionEventId'] = null; } + if (this.sectionGameId != null) { + json[r'sectionGameId'] = this.sectionGameId; + } else { + json[r'sectionGameId'] = null; + } if (this.isLinear != null) { json[r'isLinear'] = this.isLinear; } else { @@ -164,7 +174,7 @@ class GuidedPathDTO { json[r'order'] = null; } if (this.steps != null) { - json[r'steps'] = this.steps; + json[r'steps'] = this.steps!.map((v) => v.toJson()).toList(); } else { json[r'steps'] = null; } @@ -198,6 +208,7 @@ class GuidedPathDTO { description: TranslationDTO.listFromJson(json[r'description']), sectionMapId: mapValueOfType(json, r'sectionMapId'), sectionEventId: mapValueOfType(json, r'sectionEventId'), + sectionGameId: mapValueOfType(json, r'sectionGameId'), isLinear: mapValueOfType(json, r'isLinear'), requireSuccessToAdvance: mapValueOfType(json, r'requireSuccessToAdvance'), diff --git a/manager_api_new/lib/model/guided_step_dto.dart b/manager_api_new/lib/model/guided_step_dto.dart index f03fcd4..3ae3a1e 100644 --- a/manager_api_new/lib/model/guided_step_dto.dart +++ b/manager_api_new/lib/model/guided_step_dto.dart @@ -47,7 +47,7 @@ class GuidedStepDTO { List? description; - EventAddressDTOGeometry? geometry; + GeometryDTO? geometry; double? zoneRadiusMeters; @@ -151,12 +151,12 @@ class GuidedStepDTO { json[r'order'] = null; } if (this.title != null) { - json[r'title'] = this.title; + json[r'title'] = this.title!.map((v) => v.toJson()).toList(); } else { json[r'title'] = null; } if (this.description != null) { - json[r'description'] = this.description; + json[r'description'] = this.description!.map((v) => v.toJson()).toList(); } else { json[r'description'] = null; } @@ -206,12 +206,14 @@ class GuidedStepDTO { json[r'timerSeconds'] = null; } if (this.timerExpiredMessage != null) { - json[r'timerExpiredMessage'] = this.timerExpiredMessage; + json[r'timerExpiredMessage'] = + this.timerExpiredMessage!.map((v) => v.toJson()).toList(); } else { json[r'timerExpiredMessage'] = null; } if (this.quizQuestions != null) { - json[r'quizQuestions'] = this.quizQuestions; + json[r'quizQuestions'] = + this.quizQuestions!.map((v) => v.toJson()).toList(); } else { json[r'quizQuestions'] = null; } @@ -244,7 +246,7 @@ class GuidedStepDTO { order: mapValueOfType(json, r'order'), title: TranslationDTO.listFromJson(json[r'title']), description: TranslationDTO.listFromJson(json[r'description']), - geometry: EventAddressDTOGeometry.fromJson(json[r'geometry']), + geometry: GeometryDTO.fromJson(json[r'geometry']), zoneRadiusMeters: mapValueOfType(json, r'zoneRadiusMeters'), imageUrl: mapValueOfType(json, r'imageUrl'), triggerGeoPointId: mapValueOfType(json, r'triggerGeoPointId'), diff --git a/manager_api_new/lib/model/map_dto.dart b/manager_api_new/lib/model/map_dto.dart index 07f48bb..7c063fe 100644 --- a/manager_api_new/lib/model/map_dto.dart +++ b/manager_api_new/lib/model/map_dto.dart @@ -42,6 +42,8 @@ class MapDTO { this.categories = const [], this.centerLatitude, this.centerLongitude, + this.isParcours, + this.guidedPaths = const [], }); String? id; @@ -132,6 +134,10 @@ class MapDTO { String? centerLongitude; + bool? isParcours; + + List? guidedPaths; + @override bool operator ==(Object other) => identical(this, other) || @@ -164,7 +170,9 @@ class MapDTO { other.iconSource == iconSource && _deepEquality.equals(other.categories, categories) && other.centerLatitude == centerLatitude && - other.centerLongitude == centerLongitude; + other.centerLongitude == centerLongitude && + other.isParcours == isParcours && + _deepEquality.equals(other.guidedPaths, guidedPaths); @override int get hashCode => @@ -197,11 +205,13 @@ class MapDTO { (iconSource == null ? 0 : iconSource!.hashCode) + (categories == null ? 0 : categories!.hashCode) + (centerLatitude == null ? 0 : centerLatitude!.hashCode) + - (centerLongitude == null ? 0 : centerLongitude!.hashCode); + (centerLongitude == null ? 0 : centerLongitude!.hashCode) + + (isParcours == null ? 0 : isParcours!.hashCode) + + (guidedPaths == null ? 0 : guidedPaths!.hashCode); @override String toString() => - 'MapDTO[id=$id, label=$label, title=$title, description=$description, isActive=$isActive, imageId=$imageId, imageSource=$imageSource, configurationId=$configurationId, isSubSection=$isSubSection, parentId=$parentId, type=$type, dateCreation=$dateCreation, order=$order, instanceId=$instanceId, latitude=$latitude, longitude=$longitude, meterZoneGPS=$meterZoneGPS, isBeacon=$isBeacon, beaconId=$beaconId, zoom=$zoom, mapType=$mapType, mapTypeMapbox=$mapTypeMapbox, mapProvider=$mapProvider, points=$points, iconResourceId=$iconResourceId, iconSource=$iconSource, categories=$categories, centerLatitude=$centerLatitude, centerLongitude=$centerLongitude]'; + 'MapDTO[id=$id, label=$label, title=$title, description=$description, isActive=$isActive, imageId=$imageId, imageSource=$imageSource, configurationId=$configurationId, isSubSection=$isSubSection, parentId=$parentId, type=$type, dateCreation=$dateCreation, order=$order, instanceId=$instanceId, latitude=$latitude, longitude=$longitude, meterZoneGPS=$meterZoneGPS, isBeacon=$isBeacon, beaconId=$beaconId, zoom=$zoom, mapType=$mapType, mapTypeMapbox=$mapTypeMapbox, mapProvider=$mapProvider, points=$points, iconResourceId=$iconResourceId, iconSource=$iconSource, categories=$categories, centerLatitude=$centerLatitude, centerLongitude=$centerLongitude, isParcours=$isParcours, guidedPaths=$guidedPaths]'; Map toJson() { final json = {}; @@ -216,12 +226,12 @@ class MapDTO { json[r'label'] = null; } if (this.title != null) { - json[r'title'] = this.title; + json[r'title'] = this.title!.map((v) => v.toJson()).toList(); } else { json[r'title'] = null; } if (this.description != null) { - json[r'description'] = this.description; + json[r'description'] = this.description!.map((v) => v.toJson()).toList(); } else { json[r'description'] = null; } @@ -321,7 +331,7 @@ class MapDTO { json[r'mapProvider'] = null; } if (this.points != null) { - json[r'points'] = this.points; + json[r'points'] = this.points!.map((v) => v.toJson()).toList(); } else { json[r'points'] = null; } @@ -336,7 +346,7 @@ class MapDTO { json[r'iconSource'] = null; } if (this.categories != null) { - json[r'categories'] = this.categories; + json[r'categories'] = this.categories!.map((v) => v.toJson()).toList(); } else { json[r'categories'] = null; } @@ -350,6 +360,16 @@ class MapDTO { } else { json[r'centerLongitude'] = null; } + if (this.isParcours != null) { + json[r'isParcours'] = this.isParcours; + } else { + json[r'isParcours'] = null; + } + if (this.guidedPaths != null) { + json[r'guidedPaths'] = this.guidedPaths!.map((v) => v.toJson()).toList(); + } else { + json[r'guidedPaths'] = null; + } return json; } @@ -403,6 +423,8 @@ class MapDTO { categories: CategorieDTO.listFromJson(json[r'categories']), centerLatitude: mapValueOfType(json, r'centerLatitude'), centerLongitude: mapValueOfType(json, r'centerLongitude'), + isParcours: mapValueOfType(json, r'isParcours'), + guidedPaths: GuidedPathDTO.listFromJson(json[r'guidedPaths']), ); } return null; diff --git a/pubspec.lock b/pubspec.lock index 316973d..bd8ab9d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -447,7 +447,7 @@ packages: source: hosted version: "1.5.1" flutter_map: - dependency: transitive + dependency: "direct main" description: name: flutter_map sha256: "87cc8349b8fa5dccda5af50018c7374b6645334a0d680931c1fe11bce88fa5bb" @@ -737,7 +737,7 @@ packages: source: hosted version: "0.4.13" latlong2: - dependency: transitive + dependency: "direct main" description: name: latlong2 sha256: "98227922caf49e6056f91b6c56945ea1c7b166f28ffcd5fb8e72fc0b453cc8fe" diff --git a/pubspec.yaml b/pubspec.yaml index be1b9ec..7427c36 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,6 +49,8 @@ dependencies: multi_select_flutter: ^4.1.3 youtube_player_iframe: ^5.0.0 location_picker_flutter_map: ^3.0.1 + flutter_map: ^6.2.1 + latlong2: ^0.9.1 go_router: ^16.2.0 #msix: ^2.1.3