Wip changements fantastiquement rapide d'antigravity, très bonne base mais du wip

This commit is contained in:
Thomas Fransolet 2026-03-02 09:06:53 +01:00
parent 9e06afb29e
commit ded4620bf2
22 changed files with 3839 additions and 1134 deletions

View File

@ -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;
}
}
}

View File

@ -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<MapGeometryPicker> {
List<LatLng> 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<dynamic>;
points = [LatLng(coords[0].toDouble(), coords[1].toDouble())];
} else if (currentType == "LineString" || currentType == "Polygon") {
var list = widget.initialGeometry!.coordinates as List<dynamic>;
points = list.map((e) {
var pair = e as List<dynamic>;
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)),
);
}
}

View File

@ -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>(
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>(
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,7 +68,8 @@ 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,
);

View File

@ -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<AgendaDTO> onChanged;
const AgendaConfig({
Key? key,
this.color,
@ -31,21 +29,20 @@ class _AgendaConfigState extends State<AgendaConfig> {
@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<AgendaConfig> {
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<AgendaConfig> {
},
),
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<AgendaConfig> {
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());
}
);*/
}
}

View File

@ -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,
),
),
],
),
],
),
),
);
},
);
},
);
}

View File

@ -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<SectionEventDTO> onChanged;
const EventConfig({
Key? key,
required this.initialValue,
required this.onChanged,
}) : super(key: key);
@override
_EventConfigState createState() => _EventConfigState();
}
class _EventConfigState extends State<EventConfig> {
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);
});
},
),
],
),
),
);
},
),
),
],
);
}
}

View File

@ -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,
),
),
],
),
],
),
),
);
},
);
},
);
}

View File

@ -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<GameConfig> {
@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<GameTypes>(
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);
}
},
),
),
],)
],
),
],
);
}
}

View File

@ -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 = <TranslationDTO>[];
geoPointDTO.description = <TranslationDTO>[];
geoPointDTO.contents = <ContentDTO>[];
geoPointDTO.schedules = <TranslationDTO>[];
geoPointDTO.prices = <TranslationDTO>[];
geoPointDTO.phone = <TranslationDTO>[];
geoPointDTO.email = <TranslationDTO>[];
geoPointDTO.site = <TranslationDTO>[];
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<dynamic>;
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<ContentDTO> 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<ContentDTO> 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: <Widget>[
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
);
},
);
},
);
}

View File

@ -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<GuidedPathDTO> initialValue;
final String parentId;
final bool isEvent;
final bool isEscapeMode;
final ValueChanged<List<GuidedPathDTO>> 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<ParcoursConfig> {
late List<GuidedPathDTO> 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),
),
],
),
),
);
},
),
),
],
);
}
}

View File

@ -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,
),
),
],
),
],
),
),
);
},
);
},
);
}

View File

@ -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,
),
),
],
),
],
),
),
);
},
);
},
);
}

View File

@ -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<TranslationDTO> _toTranslationList(
List<TranslationAndResourceDTO>? list) =>
(list ?? [])
.map((t) => TranslationDTO(language: t.language, value: t.value))
.toList();
List<TranslationAndResourceDTO> _fromTranslationList(
List<TranslationDTO> 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<TranslationAndResourceDTO> convert for display
List<TranslationDTO> workingLabel = _toTranslationList(
question != null && question.label.isNotEmpty
? question.label
: [TranslationAndResourceDTO(language: 'FR', value: '')]);
List<ResponseDTO> 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<QuestionType>(
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<TranslationDTO> 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<TranslationDTO> 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,
),
),
],
),
],
),
),
);
},
);
},
);
}

View File

@ -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<SectionDetailScreen> {
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<dynamic> 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<SectionDetailScreen> {
],
);
} 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<SectionDetailScreen> {
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<SectionDetailScreen> {
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<SectionDetailScreen> {
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<SectionDetailScreen> {
),
),
),
),// FIELDS SECTION
), // FIELDS SECTION
Container(
//width: size.width * 0.8,
height: size.height * 0.5,
@ -326,8 +352,7 @@ class _SectionDetailScreenState extends State<SectionDetailScreen> {
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<SectionDetailScreen> {
Future<void> 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<SectionDetailScreen> {
Future<void> 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<void> 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<SectionDetailScreen> {
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<SectionDetailScreen> {
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<SectionDetailScreen> {
(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<SectionDetailScreen> {
(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<SectionDetailScreen> {
(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<SectionDetailScreen> {
(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<SectionDetailScreen> {
(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<SectionDetailScreen> {
(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<SectionDetailScreen> {
(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<SectionDetailScreen> {
(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<SectionDetailScreen> {
(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<SectionDetailScreen> {
(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<SectionDetailScreen> {
(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<Object?> 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<Uint8List?> _captureAndSharePng(GlobalKey globalKey, String sectionId) async {
Future<Uint8List?> _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<Uint8List?> _captureAndSharePng(GlobalKey globalKey, String sectionId) as
a.click();
return pngBytes;
} catch(e) {
} catch (e) {
print(e.toString());
return null;
}

View File

@ -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<SectionDTO?> showNewSection(String configurationId, AppContext appContext, BuildContext contextBuild, bool isSubSection) {
Future<SectionDTO?> 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<SectionDTO?>(
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<SectionDTO?> showNewSection(String configurationId, AppContext appContext
sectionDTO.type = SectionType.fromJson(tempOutput[0]);
},
),*/
],
),
],
),
),
),
),
actions: <Widget>[
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: <Widget>[
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,7 +156,8 @@ 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);
}

View File

@ -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<GuidedPathDTO>? 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<String, dynamic> toJson() {
final json = <String, dynamic>{};
@ -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<int>(json, r'rows'),
cols: mapValueOfType<int>(json, r'cols'),
gameType: GameTypes.fromJson(json[r'gameType']),
guidedPaths: GuidedPathDTO.listFromJson(json[r'guidedPaths']),
);
}
return null;

View File

@ -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<String, dynamic> toJson() {
final json = <String, dynamic>{};
@ -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<String>(json, r'sectionMapId'),
sectionEventId: mapValueOfType<String>(json, r'sectionEventId'),
sectionGameId: mapValueOfType<String>(json, r'sectionGameId'),
isLinear: mapValueOfType<bool>(json, r'isLinear'),
requireSuccessToAdvance:
mapValueOfType<bool>(json, r'requireSuccessToAdvance'),

View File

@ -47,7 +47,7 @@ class GuidedStepDTO {
List<TranslationDTO>? 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<int>(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<double>(json, r'zoneRadiusMeters'),
imageUrl: mapValueOfType<String>(json, r'imageUrl'),
triggerGeoPointId: mapValueOfType<int>(json, r'triggerGeoPointId'),

View File

@ -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<GuidedPathDTO>? 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<String, dynamic> toJson() {
final json = <String, dynamic>{};
@ -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<String>(json, r'centerLatitude'),
centerLongitude: mapValueOfType<String>(json, r'centerLongitude'),
isParcours: mapValueOfType<bool>(json, r'isParcours'),
guidedPaths: GuidedPathDTO.listFromJson(json[r'guidedPaths']),
);
}
return null;

View File

@ -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"

View File

@ -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