745 lines
34 KiB
Dart
745 lines
34 KiB
Dart
import 'package:diacritic/diacritic.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
|
|
import 'package:location_picker_flutter_map/location_picker_flutter_map.dart';
|
|
import 'package:manager_app/Components/confirmation_dialog.dart';
|
|
import 'package:manager_app/Components/geoloc_input_container.dart';
|
|
import 'package:manager_app/Components/common_loader.dart';
|
|
import 'package:manager_app/Components/message_notification.dart';
|
|
import 'package:manager_app/Models/managerContext.dart';
|
|
import 'package:manager_app/Screens/Configurations/Section/SubSection/Map/category_input_container.dart';
|
|
import 'package:manager_app/Components/dropDown_input_container.dart';
|
|
import 'package:manager_app/Components/fetch_section_icon.dart';
|
|
import 'package:manager_app/Components/resource_input_container.dart';
|
|
import 'package:manager_app/Components/multi_select_container.dart';
|
|
import 'package:manager_app/Components/single_select_container.dart';
|
|
import 'package:manager_app/Components/slider_input_container.dart';
|
|
import 'package:manager_app/Components/string_input_container.dart';
|
|
import 'package:manager_app/Screens/Configurations/Section/SubSection/Map/showNewOrUpdateGeoPoint.dart';
|
|
import 'package:manager_app/app_context.dart';
|
|
import 'package:manager_app/client.dart';
|
|
import 'package:manager_app/constants.dart';
|
|
import 'package:manager_api_new/api.dart';
|
|
import 'package:manager_app/Screens/Configurations/Section/SubSection/Parcours/parcours_config.dart';
|
|
|
|
import 'package:provider/provider.dart';
|
|
|
|
class MapConfig extends StatefulWidget {
|
|
final String? color;
|
|
final String? label;
|
|
final MapDTO initialValue;
|
|
final ValueChanged<MapDTO> onChanged;
|
|
const MapConfig({
|
|
Key? key,
|
|
this.color,
|
|
this.label,
|
|
required this.initialValue,
|
|
required this.onChanged,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
_MapConfigState createState() => _MapConfigState();
|
|
}
|
|
|
|
class _MapConfigState extends State<MapConfig> {
|
|
late MapDTO mapDTO;
|
|
late List<GeoPointDTO> pointsToShow = [];
|
|
//List<String>? selectedCategories = [];
|
|
String mapType = "hybrid";
|
|
String mapTypeMapBox = "standard";
|
|
|
|
String filterSearch = '';
|
|
|
|
final ValueNotifier<List<int>?> selectedCategoriesNotifier =
|
|
ValueNotifier([]);
|
|
final ValueNotifier<String?> searchNotifier = ValueNotifier("");
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
//mapDTO = MapDTO.fromJson(json.decode(widget.initialValue))!;
|
|
mapDTO = widget.initialValue;
|
|
|
|
if (mapDTO.mapType != null) {
|
|
switch (mapDTO.mapType!.value) {
|
|
case 0:
|
|
mapType = "none";
|
|
break;
|
|
case 1:
|
|
mapType = "normal";
|
|
break;
|
|
case 2:
|
|
mapType = "satellite";
|
|
break;
|
|
case 3:
|
|
mapType = "terrain";
|
|
break;
|
|
case 4:
|
|
mapType = "hybrid";
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (mapDTO.mapTypeMapbox != null) {
|
|
switch (mapDTO.mapTypeMapbox!.value) {
|
|
case 0:
|
|
mapTypeMapBox = "standard";
|
|
break;
|
|
case 1:
|
|
mapTypeMapBox = "streets";
|
|
break;
|
|
case 2:
|
|
mapTypeMapBox = "outdoors";
|
|
break;
|
|
case 3:
|
|
mapTypeMapBox = "light";
|
|
break;
|
|
case 4:
|
|
mapTypeMapBox = "dark";
|
|
break;
|
|
case 5:
|
|
mapTypeMapBox = "satellite";
|
|
break;
|
|
case 6:
|
|
mapTypeMapBox = "satellite_streets";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final appContext = Provider.of<AppContext>(context);
|
|
Size size = MediaQuery.of(context).size;
|
|
|
|
var mapProviderIn = "";
|
|
switch (mapDTO.mapProvider) {
|
|
case MapProvider.Google:
|
|
mapProviderIn = "Google";
|
|
break;
|
|
case MapProvider.MapBox:
|
|
mapProviderIn = "MapBox";
|
|
break;
|
|
default:
|
|
mapProviderIn = "Google";
|
|
break;
|
|
}
|
|
|
|
return DefaultTabController(
|
|
length: 2,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
_buildMapHeader(size, mapProviderIn),
|
|
TabBar(
|
|
labelColor: kPrimaryColor,
|
|
unselectedLabelColor: Colors.grey,
|
|
indicatorColor: kPrimaryColor,
|
|
tabs: [
|
|
Tab(icon: Icon(Icons.map), text: "Points d'Intérêt"),
|
|
Tab(icon: Icon(Icons.route), text: "Parcours"),
|
|
],
|
|
),
|
|
Container(
|
|
height: 700,
|
|
child: TabBarView(
|
|
children: [
|
|
// Tab 1: Configuration & Points
|
|
SingleChildScrollView(
|
|
child: Column(
|
|
children: [
|
|
FutureBuilder(
|
|
future: getGeoPoints(
|
|
(appContext.getContext() as ManagerAppContext)
|
|
.clientAPI!),
|
|
builder: (context, AsyncSnapshot<dynamic> snapshot) {
|
|
if (snapshot.connectionState ==
|
|
ConnectionState.waiting) {
|
|
return Center(child: CommonLoader());
|
|
} else {
|
|
if (snapshot.connectionState ==
|
|
ConnectionState.done) {
|
|
mapDTO.points = snapshot.data;
|
|
pointsToShow = mapDTO.points!;
|
|
pointsToShow.sort((a, b) => a.title!
|
|
.firstWhere((t) => t.language == 'FR')
|
|
.value!
|
|
.toLowerCase()
|
|
.compareTo(b.title!
|
|
.firstWhere((t) => t.language == 'FR')
|
|
.value!
|
|
.toLowerCase()));
|
|
selectedCategoriesNotifier.value = mapDTO
|
|
.categories!
|
|
.map((categorie) => categorie.id!)
|
|
.toList();
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(25),
|
|
border: Border.all(
|
|
width: 1.5, color: kSecond)),
|
|
child: Stack(children: [
|
|
Container(
|
|
constraints:
|
|
BoxConstraints(minHeight: 100),
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(
|
|
top: 95,
|
|
left: 10,
|
|
right: 10,
|
|
bottom: 10),
|
|
child:
|
|
ValueListenableBuilder<String?>(
|
|
valueListenable:
|
|
searchNotifier,
|
|
builder: (context,
|
|
searchValue, child) {
|
|
return ValueListenableBuilder<
|
|
List<int>?>(
|
|
valueListenable:
|
|
selectedCategoriesNotifier,
|
|
builder: (context,
|
|
selectedCategories,
|
|
child) {
|
|
if (selectedCategories ==
|
|
null ||
|
|
selectedCategories
|
|
.length ==
|
|
0) {
|
|
pointsToShow = mapDTO
|
|
.points!
|
|
.where((point) =>
|
|
point
|
|
.categorieId ==
|
|
null)
|
|
.toList();
|
|
} else {
|
|
pointsToShow = mapDTO
|
|
.points!
|
|
.where((point) =>
|
|
selectedCategories.any((tps) =>
|
|
point.categorieId ==
|
|
null ||
|
|
point.categorieId ==
|
|
tps))
|
|
.toList();
|
|
}
|
|
|
|
pointsToShow = searchValue !=
|
|
null &&
|
|
searchValue
|
|
.trim()
|
|
.isNotEmpty
|
|
? pointsToShow
|
|
.where((GeoPointDTO pointGeo) => removeDiacritics(pointGeo
|
|
.title!
|
|
.firstWhere((t) =>
|
|
t.language ==
|
|
"FR")
|
|
.value!
|
|
.toUpperCase())
|
|
.contains(
|
|
removeDiacritics(
|
|
searchValue.toUpperCase())))
|
|
.toList()
|
|
: pointsToShow;
|
|
|
|
return GridView
|
|
.builder(
|
|
shrinkWrap:
|
|
true,
|
|
gridDelegate:
|
|
new SliverGridDelegateWithFixedCrossAxisCount(
|
|
crossAxisCount:
|
|
8),
|
|
itemCount:
|
|
pointsToShow
|
|
.length,
|
|
itemBuilder:
|
|
(BuildContext
|
|
context,
|
|
int index) {
|
|
return Container(
|
|
decoration: boxDecoration(
|
|
pointsToShow[
|
|
index],
|
|
appContext),
|
|
padding:
|
|
const EdgeInsets
|
|
.all(
|
|
5),
|
|
margin: EdgeInsets.symmetric(
|
|
vertical:
|
|
10,
|
|
horizontal:
|
|
10),
|
|
child: getElement(
|
|
index,
|
|
pointsToShow[
|
|
index],
|
|
size,
|
|
appContext),
|
|
);
|
|
});
|
|
});
|
|
}),
|
|
),
|
|
),
|
|
Positioned(
|
|
top: 10,
|
|
left: 10,
|
|
child: Text(
|
|
"Points géographiques",
|
|
style: TextStyle(fontSize: 15),
|
|
),
|
|
),
|
|
Positioned(
|
|
top: 10,
|
|
left: 175,
|
|
child: MultiSelectContainer(
|
|
label: null,
|
|
color: kSecond,
|
|
width: size.width * 0.45,
|
|
initialValue: mapDTO.categories!
|
|
.where((cat) =>
|
|
selectedCategoriesNotifier
|
|
.value!
|
|
.contains(cat.id))
|
|
.map((categorie) => categorie
|
|
.label!
|
|
.firstWhere((element) =>
|
|
element.language ==
|
|
'FR')
|
|
.value!)
|
|
.toList(),
|
|
isMultiple: true,
|
|
isHTMLLabel: true,
|
|
values: mapDTO.categories!
|
|
.map((categorie) => categorie
|
|
.label!
|
|
.firstWhere((element) =>
|
|
element.language ==
|
|
'FR')
|
|
.value!)
|
|
.toList(),
|
|
onChanged: (value) {
|
|
var tempOutput =
|
|
new List<String>.from(value);
|
|
selectedCategoriesNotifier.value =
|
|
mapDTO.categories!
|
|
.where((c) => tempOutput
|
|
.contains(c.label!
|
|
.firstWhere(
|
|
(element) =>
|
|
element
|
|
.language ==
|
|
'FR')
|
|
.value!))
|
|
.map((cat) => cat.id!)
|
|
.toList();
|
|
},
|
|
)),
|
|
Positioned(
|
|
top: 0,
|
|
right: 150,
|
|
child: Container(
|
|
height: size.height * 0.1,
|
|
constraints:
|
|
BoxConstraints(minHeight: 85),
|
|
child: StringInputContainer(
|
|
label: "Recherche:",
|
|
isSmall: true,
|
|
fontSize: 15,
|
|
fontSizeText: 15,
|
|
onChanged: (String value) {
|
|
searchNotifier.value = value;
|
|
},
|
|
),
|
|
),
|
|
),
|
|
Positioned(
|
|
top: 10,
|
|
right: 10,
|
|
child: InkWell(
|
|
onTap: () {
|
|
showNewOrUpdateGeoPoint(
|
|
mapDTO, null,
|
|
(GeoPointDTO geoPoint) async {
|
|
try {
|
|
await (appContext.getContext()
|
|
as ManagerAppContext)
|
|
.clientAPI!
|
|
.sectionMapApi!
|
|
.sectionMapCreate(
|
|
mapDTO.id!, geoPoint);
|
|
showNotification(
|
|
kSuccess,
|
|
kWhite,
|
|
'Le point géographique a été créé avec succès',
|
|
context,
|
|
null);
|
|
setState(() {
|
|
// refresh ui
|
|
print("Refresh UI");
|
|
});
|
|
} catch (e) {
|
|
showNotification(
|
|
kError,
|
|
kWhite,
|
|
'Une erreur est survenue lors de la création du point géographique',
|
|
context,
|
|
null);
|
|
}
|
|
}, appContext, context);
|
|
},
|
|
child: Container(
|
|
height: 40,
|
|
width: 40,
|
|
child: Icon(
|
|
Icons.add,
|
|
color: kTextLightColor,
|
|
size: 25.0,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: kSuccess,
|
|
shape: BoxShape.rectangle,
|
|
borderRadius:
|
|
BorderRadius.circular(20.0),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: kSecond,
|
|
spreadRadius: 0.5,
|
|
blurRadius: 5,
|
|
offset: Offset(0,
|
|
1.5), // changes position of shadow
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
)
|
|
]),
|
|
),
|
|
);
|
|
} else {
|
|
return Center(
|
|
child: Text(
|
|
"Une erreur est survenue lors de la récupération des points géographiques"),
|
|
);
|
|
}
|
|
}
|
|
}),
|
|
],
|
|
),
|
|
),
|
|
// Tab 2: Parcours
|
|
ParcoursConfig(
|
|
initialValue: mapDTO.guidedPaths ?? [],
|
|
parentId: mapDTO.id!,
|
|
isEvent: false,
|
|
onChanged: (paths) {
|
|
setState(() {
|
|
mapDTO.guidedPaths = paths;
|
|
widget.onChanged(mapDTO);
|
|
});
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<List<GeoPointDTO>?> getGeoPoints(Client client) async {
|
|
List<GeoPointDTO>? geoPoints = await client.sectionMapApi!
|
|
.sectionMapGetAllGeoPointsFromSection(widget.initialValue.id!);
|
|
return geoPoints ?? [];
|
|
}
|
|
|
|
getElement(int index, GeoPointDTO? point, Size size, AppContext appContext) {
|
|
return Container(
|
|
width: double.infinity,
|
|
height: double.infinity,
|
|
child: Stack(
|
|
children: [
|
|
Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: HtmlWidget(
|
|
point != null
|
|
? point.title == null
|
|
? ""
|
|
: point.title![0].value!
|
|
: "",
|
|
//textAlign: TextAlign.left,
|
|
customStylesBuilder: (element) {
|
|
return {'text-align': 'center'};
|
|
}, textStyle: TextStyle(fontSize: 20)),
|
|
),
|
|
),
|
|
Positioned(
|
|
top: 0,
|
|
right: 0,
|
|
child: Icon(
|
|
getSectionIcon(SectionType.Map),
|
|
color: kSecond,
|
|
size: 18.0,
|
|
),
|
|
),
|
|
Positioned(
|
|
bottom: 0,
|
|
left: 0,
|
|
child: InkWell(
|
|
onTap: () {
|
|
showNewOrUpdateGeoPoint(mapDTO, pointsToShow[index],
|
|
(GeoPointDTO geoPoint) async {
|
|
try {
|
|
await (appContext.getContext() as ManagerAppContext)
|
|
.clientAPI!
|
|
.sectionMapApi!
|
|
.sectionMapUpdate(geoPoint);
|
|
showNotification(
|
|
kSuccess,
|
|
kWhite,
|
|
'Le point géographique a été mis à jour avec succès',
|
|
context,
|
|
null);
|
|
setState(() {
|
|
// refresh ui
|
|
print("Refresh UI");
|
|
});
|
|
} catch (e) {
|
|
showNotification(
|
|
kError,
|
|
kWhite,
|
|
'Une erreur est survenue lors de la mise à jour du point géographique',
|
|
context,
|
|
null);
|
|
}
|
|
|
|
/*setState(() {
|
|
var pointToUpdate = pointsToShow[index];
|
|
var pointToUpdateMapDTOPoints = mapDTO.points!.firstWhere((p) => p.longitude == pointToUpdate.longitude && p.latitude == pointToUpdate.latitude);
|
|
var mapDTOPointsIndex = mapDTO.points!.indexOf(pointToUpdateMapDTOPoints);
|
|
mapDTO.points![mapDTOPointsIndex] = pointToUpdate;
|
|
mapDTO.points!.sort((a, b) => a.title!.firstWhere((t) => t.language == 'FR').value!.toLowerCase().compareTo(b.title!.firstWhere((t) => t.language == 'FR').value!.toLowerCase()));
|
|
|
|
//widget.onChanged(jsonEncode(mapDTO).toString());
|
|
widget.onChanged(mapDTO);
|
|
});*/
|
|
}, appContext, context);
|
|
},
|
|
child: Icon(
|
|
Icons.edit,
|
|
color: kPrimaryColor,
|
|
size: 20.0,
|
|
)),
|
|
),
|
|
Positioned(
|
|
bottom: 0,
|
|
right: 0,
|
|
child: InkWell(
|
|
onTap: () async {
|
|
showConfirmationDialog(
|
|
"Êtes-vous sûr de vouloir supprimer ce point géographique ?",
|
|
() {}, () async {
|
|
try {
|
|
var pointToRemove = pointsToShow[index];
|
|
(appContext.getContext() as ManagerAppContext)
|
|
.clientAPI!
|
|
.sectionMapApi!
|
|
.sectionMapDelete(pointToRemove.id!);
|
|
showNotification(
|
|
kSuccess,
|
|
kWhite,
|
|
'Le point géographique a été supprimé avec succès',
|
|
context,
|
|
null);
|
|
// refresh UI
|
|
setState(() {
|
|
print("Refresh UI");
|
|
});
|
|
} catch (e) {
|
|
showNotification(
|
|
kError,
|
|
kWhite,
|
|
'Une erreur est survenue lors de la suppression du point géographique',
|
|
context,
|
|
null);
|
|
}
|
|
}, context);
|
|
},
|
|
child: Icon(
|
|
Icons.delete,
|
|
color: kError,
|
|
size: 20.0,
|
|
)),
|
|
)
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildMapHeader(Size size, String mapProviderIn) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(16.0),
|
|
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(15),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.grey.withOpacity(0.1),
|
|
spreadRadius: 2,
|
|
blurRadius: 5,
|
|
offset: Offset(0, 3),
|
|
),
|
|
],
|
|
border: Border.all(color: kPrimaryColor.withOpacity(0.2)),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
children: [
|
|
SingleSelectContainer(
|
|
label: "Service :",
|
|
color: Colors.black,
|
|
initialValue: mapProviderIn,
|
|
inputValues: map_providers,
|
|
onChanged: (String value) {
|
|
switch (value) {
|
|
case "Google":
|
|
mapDTO.mapProvider = MapProvider.Google;
|
|
break;
|
|
case "MapBox":
|
|
mapDTO.mapProvider = MapProvider.MapBox;
|
|
break;
|
|
}
|
|
widget.onChanged(mapDTO);
|
|
}),
|
|
GeolocInputContainer(
|
|
label: "Point de centrage :",
|
|
initialValue: mapDTO.centerLatitude != null &&
|
|
mapDTO.centerLongitude != null
|
|
? LatLong(double.parse(mapDTO.centerLatitude!),
|
|
double.parse(mapDTO.centerLongitude!))
|
|
: null,
|
|
color: kPrimaryColor,
|
|
onChanged: (LatLong? localisation) {
|
|
if (localisation != null) {
|
|
mapDTO.centerLongitude =
|
|
localisation.longitude.toString();
|
|
mapDTO.centerLatitude = localisation.latitude.toString();
|
|
}
|
|
widget.onChanged(mapDTO);
|
|
},
|
|
isSmall: true),
|
|
ResourceInputContainer(
|
|
label: "Icône :",
|
|
initialValue: mapDTO.iconResourceId,
|
|
color: kPrimaryColor,
|
|
imageFit: BoxFit.contain,
|
|
onChanged: (ResourceDTO resource) {
|
|
if (resource.id == null) {
|
|
mapDTO.iconSource = null;
|
|
mapDTO.iconResourceId = null;
|
|
} else {
|
|
mapDTO.iconResourceId = resource.id;
|
|
mapDTO.iconSource = resource.url;
|
|
}
|
|
widget.onChanged(mapDTO);
|
|
},
|
|
isSmall: true),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
children: [
|
|
if (mapDTO.mapProvider == MapProvider.Google)
|
|
DropDownInputContainer(
|
|
label: "Type :",
|
|
values: map_types,
|
|
initialValue: mapType,
|
|
onChange: (String? value) {
|
|
mapDTO.mapType = MapTypeApp.fromJson(value);
|
|
widget.onChanged(mapDTO);
|
|
},
|
|
),
|
|
if (mapDTO.mapProvider == MapProvider.MapBox)
|
|
DropDownInputContainer(
|
|
label: "Type :",
|
|
values: map_types_mapBox,
|
|
initialValue: mapTypeMapBox,
|
|
onChange: (String? value) {
|
|
mapDTO.mapTypeMapbox = MapTypeMapBox.fromJson(value);
|
|
widget.onChanged(mapDTO);
|
|
},
|
|
),
|
|
SliderInputContainer(
|
|
label: "Zoom :",
|
|
initialValue:
|
|
mapDTO.zoom != null ? mapDTO.zoom!.toDouble() : 18,
|
|
color: kPrimaryColor,
|
|
min: 0,
|
|
max: 30,
|
|
onChanged: (double value) {
|
|
mapDTO.zoom = value.toInt();
|
|
widget.onChanged(mapDTO);
|
|
},
|
|
),
|
|
Container(
|
|
height: 70,
|
|
child: CategoryInputContainer(
|
|
label: "Catégories :",
|
|
initialValue:
|
|
mapDTO.categories != null ? mapDTO.categories! : [],
|
|
color: kPrimaryColor,
|
|
onChanged: (List<CategorieDTO>? value) {
|
|
if (value != null) {
|
|
mapDTO.categories = value;
|
|
if (mapDTO.points != null) {
|
|
mapDTO.points!.forEach((p) {
|
|
if (p.categorieId != null &&
|
|
!mapDTO.categories!.map((c) => c.id).any(
|
|
(e) => e != null && e == p.categorieId)) {
|
|
p.categorieId = null;
|
|
}
|
|
});
|
|
}
|
|
widget.onChanged(mapDTO);
|
|
}
|
|
},
|
|
),
|
|
)
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
boxDecoration(GeoPointDTO geoPointDTO, appContext) {
|
|
return BoxDecoration(
|
|
color: kBackgroundColor,
|
|
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
|
|
),
|
|
],
|
|
);
|
|
}
|