Initialisation du projet geosector complet (web + flutter)
This commit is contained in:
898
flutt/lib/presentation/admin/admin_map_page.dart
Normal file
898
flutt/lib/presentation/admin/admin_map_page.dart
Normal file
@@ -0,0 +1,898 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:geosector_app/presentation/widgets/mapbox_map.dart';
|
||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||
import 'package:geosector_app/core/services/location_service.dart';
|
||||
import 'package:geosector_app/core/data/models/sector_model.dart';
|
||||
import 'package:geosector_app/core/data/models/passage_model.dart';
|
||||
import '../../shared/app_theme.dart';
|
||||
|
||||
class AdminMapPage extends StatefulWidget {
|
||||
const AdminMapPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<AdminMapPage> createState() => _AdminMapPageState();
|
||||
}
|
||||
|
||||
class _AdminMapPageState extends State<AdminMapPage> {
|
||||
// Contrôleur de carte
|
||||
final MapController _mapController = MapController();
|
||||
|
||||
// Position actuelle et zoom
|
||||
LatLng _currentPosition =
|
||||
const LatLng(48.117266, -1.6777926); // Position initiale sur Rennes
|
||||
double _currentZoom = 12.0; // Zoom initial
|
||||
|
||||
// Données des secteurs et passages
|
||||
final List<Map<String, dynamic>> _sectors = [];
|
||||
final List<Map<String, dynamic>> _passages = [];
|
||||
|
||||
// États
|
||||
bool _editMode = false;
|
||||
int? _selectedSectorId;
|
||||
List<DropdownMenuItem<int?>> _sectorItems = [];
|
||||
|
||||
// Référence à la boîte Hive pour les paramètres
|
||||
late Box _settingsBox;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initSettings().then((_) {
|
||||
_loadSectors();
|
||||
_loadPassages();
|
||||
});
|
||||
}
|
||||
|
||||
// Initialiser la boîte de paramètres et charger les préférences
|
||||
Future<void> _initSettings() async {
|
||||
// Ouvrir la boîte de paramètres si elle n'est pas déjà ouverte
|
||||
if (!Hive.isBoxOpen(AppKeys.settingsBoxName)) {
|
||||
_settingsBox = await Hive.openBox(AppKeys.settingsBoxName);
|
||||
} else {
|
||||
_settingsBox = Hive.box(AppKeys.settingsBoxName);
|
||||
}
|
||||
|
||||
// Charger le secteur sélectionné
|
||||
_selectedSectorId = _settingsBox.get('admin_selectedSectorId');
|
||||
|
||||
// Charger la position et le zoom
|
||||
final double? savedLat = _settingsBox.get('admin_mapLat');
|
||||
final double? savedLng = _settingsBox.get('admin_mapLng');
|
||||
final double? savedZoom = _settingsBox.get('admin_mapZoom');
|
||||
|
||||
if (savedLat != null && savedLng != null) {
|
||||
_currentPosition = LatLng(savedLat, savedLng);
|
||||
}
|
||||
|
||||
if (savedZoom != null) {
|
||||
_currentZoom = savedZoom;
|
||||
}
|
||||
}
|
||||
|
||||
// Sauvegarder les paramètres utilisateur
|
||||
void _saveSettings() {
|
||||
// Sauvegarder le secteur sélectionné
|
||||
if (_selectedSectorId != null) {
|
||||
_settingsBox.put('admin_selectedSectorId', _selectedSectorId);
|
||||
}
|
||||
|
||||
// Sauvegarder la position et le zoom actuels
|
||||
_settingsBox.put('admin_mapLat', _currentPosition.latitude);
|
||||
_settingsBox.put('admin_mapLng', _currentPosition.longitude);
|
||||
_settingsBox.put('admin_mapZoom', _currentZoom);
|
||||
}
|
||||
|
||||
// Charger les secteurs depuis la boîte Hive
|
||||
void _loadSectors() {
|
||||
try {
|
||||
final sectorsBox = Hive.box<SectorModel>(AppKeys.sectorsBoxName);
|
||||
final sectors = sectorsBox.values.toList();
|
||||
|
||||
setState(() {
|
||||
_sectors.clear();
|
||||
|
||||
for (final sector in sectors) {
|
||||
final List<List<double>> coordinates = sector.getCoordinates();
|
||||
final List<LatLng> points =
|
||||
coordinates.map((coord) => LatLng(coord[0], coord[1])).toList();
|
||||
|
||||
if (points.isNotEmpty) {
|
||||
_sectors.add({
|
||||
'id': sector.id,
|
||||
'name': sector.libelle,
|
||||
'color': _hexToColor(sector.color),
|
||||
'points': points,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Si un secteur était sélectionné précédemment, le centrer
|
||||
// Mettre à jour les items de la combobox de secteurs
|
||||
_updateSectorItems();
|
||||
|
||||
if (_selectedSectorId != null &&
|
||||
_sectors.any((s) => s['id'] == _selectedSectorId)) {
|
||||
_centerMapOnSpecificSector(_selectedSectorId!);
|
||||
}
|
||||
// Sinon, centrer la carte sur tous les secteurs
|
||||
else if (_sectors.isNotEmpty) {
|
||||
_centerMapOnSectors();
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors du chargement des secteurs: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Charger les passages depuis la boîte Hive
|
||||
void _loadPassages() {
|
||||
try {
|
||||
// Récupérer la boîte des passages
|
||||
final passagesBox = Hive.box<PassageModel>(AppKeys.passagesBoxName);
|
||||
|
||||
// Créer une nouvelle liste temporaire
|
||||
final List<Map<String, dynamic>> newPassages = [];
|
||||
|
||||
// Parcourir tous les passages dans la boîte
|
||||
for (var i = 0; i < passagesBox.length; i++) {
|
||||
final passage = passagesBox.getAt(i);
|
||||
if (passage != null) {
|
||||
// Vérifier si les coordonnées GPS sont valides
|
||||
final lat = double.tryParse(passage.gpsLat);
|
||||
final lng = double.tryParse(passage.gpsLng);
|
||||
|
||||
// Filtrer par secteur si un secteur est sélectionné
|
||||
if (_selectedSectorId != null &&
|
||||
passage.fkSector != _selectedSectorId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lat != null && lng != null) {
|
||||
// Obtenir la couleur du type de passage
|
||||
Color passageColor = Colors.grey; // Couleur par défaut
|
||||
|
||||
// Vérifier si le type de passage existe dans AppKeys.typesPassages
|
||||
if (AppKeys.typesPassages.containsKey(passage.fkType)) {
|
||||
// Utiliser la couleur1 du type de passage
|
||||
final colorValue =
|
||||
AppKeys.typesPassages[passage.fkType]!['couleur1'] as int;
|
||||
passageColor = Color(colorValue);
|
||||
|
||||
// Ajouter le passage à la liste temporaire
|
||||
newPassages.add({
|
||||
'id': passage.id,
|
||||
'position': LatLng(lat, lng),
|
||||
'type': passage.fkType,
|
||||
'color': passageColor,
|
||||
'model': passage, // Ajouter le modèle complet
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mettre à jour la liste des passages dans l'état
|
||||
setState(() {
|
||||
_passages.clear();
|
||||
_passages.addAll(newPassages);
|
||||
});
|
||||
|
||||
// Sauvegarder les paramètres après chargement des passages
|
||||
_saveSettings();
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors du chargement des passages: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Convertir une couleur hexadécimale en Color
|
||||
Color _hexToColor(String hexColor) {
|
||||
// Supprimer le # si présent
|
||||
final String colorStr =
|
||||
hexColor.startsWith('#') ? hexColor.substring(1) : hexColor;
|
||||
|
||||
// Ajouter FF pour l'opacité si nécessaire (6 caractères -> 8 caractères)
|
||||
final String fullColorStr = colorStr.length == 6 ? 'FF$colorStr' : colorStr;
|
||||
|
||||
// Convertir en entier et créer la couleur
|
||||
return Color(int.parse(fullColorStr, radix: 16));
|
||||
}
|
||||
|
||||
// Centrer la carte sur tous les secteurs
|
||||
void _centerMapOnSectors() {
|
||||
if (_sectors.isEmpty) return;
|
||||
|
||||
// Trouver les limites de tous les secteurs
|
||||
double minLat = 90.0;
|
||||
double maxLat = -90.0;
|
||||
double minLng = 180.0;
|
||||
double maxLng = -180.0;
|
||||
|
||||
for (final sector in _sectors) {
|
||||
final points = sector['points'] as List<LatLng>;
|
||||
for (final point in points) {
|
||||
minLat = point.latitude < minLat ? point.latitude : minLat;
|
||||
maxLat = point.latitude > maxLat ? point.latitude : maxLat;
|
||||
minLng = point.longitude < minLng ? point.longitude : minLng;
|
||||
maxLng = point.longitude > maxLng ? point.longitude : maxLng;
|
||||
}
|
||||
}
|
||||
|
||||
// Ajouter un padding aux limites pour s'assurer que tous les secteurs sont entièrement visibles
|
||||
// avec une marge autour (5% de la taille totale)
|
||||
final latPadding = (maxLat - minLat) * 0.05;
|
||||
final lngPadding = (maxLng - minLng) * 0.05;
|
||||
|
||||
minLat -= latPadding;
|
||||
maxLat += latPadding;
|
||||
minLng -= lngPadding;
|
||||
maxLng += lngPadding;
|
||||
|
||||
// Calculer le centre
|
||||
final centerLat = (minLat + maxLat) / 2;
|
||||
final centerLng = (minLng + maxLng) / 2;
|
||||
|
||||
// Calculer le zoom approprié en tenant compte des dimensions de l'écran
|
||||
final mapWidth = MediaQuery.of(context).size.width;
|
||||
final mapHeight = MediaQuery.of(context).size.height *
|
||||
0.7; // Estimation de la hauteur de la carte
|
||||
final zoom = _calculateOptimalZoom(
|
||||
minLat, maxLat, minLng, maxLng, mapWidth, mapHeight);
|
||||
|
||||
// Centrer la carte sur ces limites avec animation
|
||||
_mapController.move(LatLng(centerLat, centerLng), zoom);
|
||||
|
||||
// Mettre à jour l'état pour refléter la nouvelle position
|
||||
setState(() {
|
||||
_currentPosition = LatLng(centerLat, centerLng);
|
||||
_currentZoom = zoom;
|
||||
});
|
||||
|
||||
debugPrint('Carte centrée sur tous les secteurs avec zoom: $zoom');
|
||||
}
|
||||
|
||||
// Mettre à jour les items de la combobox de secteurs
|
||||
void _updateSectorItems() {
|
||||
// Créer l'item "Tous les secteurs"
|
||||
final List<DropdownMenuItem<int?>> items = [
|
||||
const DropdownMenuItem<int?>(
|
||||
value: null,
|
||||
child: Text('Tous les secteurs'),
|
||||
),
|
||||
];
|
||||
|
||||
// Ajouter tous les secteurs
|
||||
for (final sector in _sectors) {
|
||||
items.add(
|
||||
DropdownMenuItem<int?>(
|
||||
value: sector['id'] as int,
|
||||
child: Text(sector['name'] as String),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_sectorItems = items;
|
||||
});
|
||||
}
|
||||
|
||||
// Centrer la carte sur un secteur spécifique
|
||||
void _centerMapOnSpecificSector(int sectorId) {
|
||||
final sectorIndex = _sectors.indexWhere((s) => s['id'] == sectorId);
|
||||
if (sectorIndex == -1) return;
|
||||
|
||||
// Mettre à jour le secteur sélectionné
|
||||
_selectedSectorId = sectorId;
|
||||
|
||||
final sector = _sectors[sectorIndex];
|
||||
final points = sector['points'] as List<LatLng>;
|
||||
final sectorName = sector['name'] as String;
|
||||
|
||||
debugPrint(
|
||||
'Centrage sur le secteur: $sectorName (ID: $sectorId) avec ${points.length} points');
|
||||
|
||||
if (points.isEmpty) {
|
||||
debugPrint('Aucun point dans ce secteur!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Trouver les limites du secteur
|
||||
double minLat = 90.0;
|
||||
double maxLat = -90.0;
|
||||
double minLng = 180.0;
|
||||
double maxLng = -180.0;
|
||||
|
||||
for (final point in points) {
|
||||
minLat = point.latitude < minLat ? point.latitude : minLat;
|
||||
maxLat = point.latitude > maxLat ? point.latitude : maxLat;
|
||||
minLng = point.longitude < minLng ? point.longitude : minLng;
|
||||
maxLng = point.longitude > maxLng ? point.longitude : maxLng;
|
||||
}
|
||||
|
||||
// Vérifier si les coordonnées sont valides
|
||||
if (minLat >= maxLat || minLng >= maxLng) {
|
||||
debugPrint('Coordonnées invalides pour le secteur $sectorName');
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculer la taille du secteur
|
||||
final latSpan = maxLat - minLat;
|
||||
final lngSpan = maxLng - minLng;
|
||||
|
||||
// Ajouter un padding minimal aux limites pour s'assurer que le secteur est bien visible
|
||||
final double latPadding, lngPadding;
|
||||
if (latSpan < 0.01 || lngSpan < 0.01) {
|
||||
// Pour les très petits secteurs, utiliser un padding très réduit
|
||||
latPadding = 0.0003;
|
||||
lngPadding = 0.0003;
|
||||
} else if (latSpan < 0.05 || lngSpan < 0.05) {
|
||||
// Pour les petits secteurs, padding réduit
|
||||
latPadding = 0.0005;
|
||||
lngPadding = 0.0005;
|
||||
} else {
|
||||
// Pour les secteurs plus grands, utiliser un pourcentage minimal
|
||||
latPadding = latSpan * 0.03; // 3% au lieu de 10%
|
||||
lngPadding = lngSpan * 0.03;
|
||||
}
|
||||
|
||||
minLat -= latPadding;
|
||||
maxLat += latPadding;
|
||||
minLng -= lngPadding;
|
||||
maxLng += lngPadding;
|
||||
|
||||
// Calculer le centre
|
||||
final centerLat = (minLat + maxLat) / 2;
|
||||
final centerLng = (minLng + maxLng) / 2;
|
||||
|
||||
// Déterminer le zoom approprié en fonction de la taille du secteur
|
||||
double zoom;
|
||||
|
||||
// Pour les très petits secteurs (comme des quartiers), utiliser un zoom fixe élevé
|
||||
if (latSpan < 0.01 && lngSpan < 0.01) {
|
||||
zoom = 16.0; // Zoom élevé pour les petits quartiers
|
||||
} else if (latSpan < 0.02 && lngSpan < 0.02) {
|
||||
zoom = 15.0; // Zoom élevé pour les petits quartiers
|
||||
} else if (latSpan < 0.05 && lngSpan < 0.05) {
|
||||
zoom =
|
||||
13.0; // Zoom pour les secteurs de taille moyenne (quelques quartiers)
|
||||
} else if (latSpan < 0.1 && lngSpan < 0.1) {
|
||||
zoom = 12.0; // Zoom pour les grands secteurs (ville)
|
||||
} else {
|
||||
// Pour les secteurs plus grands, calculer le zoom
|
||||
final mapWidth = MediaQuery.of(context).size.width;
|
||||
final mapHeight = MediaQuery.of(context).size.height * 0.7;
|
||||
zoom = _calculateOptimalZoom(
|
||||
minLat, maxLat, minLng, maxLng, mapWidth, mapHeight);
|
||||
}
|
||||
|
||||
// Centrer la carte sur le secteur avec animation
|
||||
_mapController.move(LatLng(centerLat, centerLng), zoom);
|
||||
|
||||
// Mettre à jour l'état pour refléter la nouvelle position
|
||||
setState(() {
|
||||
_currentPosition = LatLng(centerLat, centerLng);
|
||||
_currentZoom = zoom;
|
||||
});
|
||||
|
||||
// Recharger les passages pour appliquer le filtre par secteur
|
||||
_loadPassages();
|
||||
}
|
||||
|
||||
// Calculer le zoom optimal pour afficher une zone géographique dans la fenêtre de la carte
|
||||
double _calculateOptimalZoom(double minLat, double maxLat, double minLng,
|
||||
double maxLng, double mapWidth, double mapHeight) {
|
||||
// Vérifier si les coordonnées sont valides
|
||||
if (minLat >= maxLat || minLng >= maxLng) {
|
||||
debugPrint('Coordonnées invalides pour le calcul du zoom');
|
||||
return 12.0; // Valeur par défaut raisonnable
|
||||
}
|
||||
|
||||
// Calculer la taille en degrés
|
||||
final latSpan = maxLat - minLat;
|
||||
final lngSpan = maxLng - minLng;
|
||||
|
||||
// Ajouter un facteur de sécurité pour éviter les divisions par zéro
|
||||
if (latSpan < 0.0000001 || lngSpan < 0.0000001) {
|
||||
return 15.0; // Zoom élevé pour un point très précis
|
||||
}
|
||||
|
||||
// Formule simplifiée pour le calcul du zoom
|
||||
double zoom;
|
||||
|
||||
if (latSpan < 0.005 || lngSpan < 0.005) {
|
||||
// Très petite zone (quartier)
|
||||
zoom = 16.0;
|
||||
} else if (latSpan < 0.01 || lngSpan < 0.01) {
|
||||
// Petite zone (quartier)
|
||||
zoom = 15.0;
|
||||
} else if (latSpan < 0.02 || lngSpan < 0.02) {
|
||||
// Petite zone (plusieurs quartiers)
|
||||
zoom = 14.0;
|
||||
} else if (latSpan < 0.05 || lngSpan < 0.05) {
|
||||
// Zone moyenne (ville)
|
||||
zoom = 13.0;
|
||||
} else if (latSpan < 0.2 || lngSpan < 0.2) {
|
||||
// Grande zone (agglomération)
|
||||
zoom = 11.0;
|
||||
} else if (latSpan < 0.5 || lngSpan < 0.5) {
|
||||
// Très grande zone (département)
|
||||
zoom = 9.0;
|
||||
} else if (latSpan < 2.0 || lngSpan < 2.0) {
|
||||
// Région
|
||||
zoom = 7.0;
|
||||
} else if (latSpan < 5.0 || lngSpan < 5.0) {
|
||||
// Pays
|
||||
zoom = 5.0;
|
||||
} else {
|
||||
// Continent ou plus
|
||||
zoom = 3.0;
|
||||
}
|
||||
|
||||
return zoom;
|
||||
}
|
||||
|
||||
// Obtenir la position actuelle de l'utilisateur
|
||||
Future<void> _getUserLocation() async {
|
||||
try {
|
||||
// Afficher un indicateur de chargement
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Recherche de votre position...'),
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
|
||||
// Obtenir la position actuelle via le service de géolocalisation
|
||||
final position = await LocationService.getCurrentPosition();
|
||||
|
||||
if (position != null) {
|
||||
// Mettre à jour la position sur la carte
|
||||
_updateMapPosition(position, zoom: 17);
|
||||
|
||||
// Sauvegarder la nouvelle position
|
||||
_settingsBox.put('admin_mapLat', position.latitude);
|
||||
_settingsBox.put('admin_mapLng', position.longitude);
|
||||
|
||||
// Informer l'utilisateur
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Position actualisée'),
|
||||
backgroundColor: Colors.green,
|
||||
duration: Duration(seconds: 1),
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Informer l'utilisateur en cas d'échec
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
'Impossible d\'obtenir votre position. Vérifiez vos paramètres de localisation.'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Gérer les erreurs
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Erreur: $e'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode pour mettre à jour la position sur la carte
|
||||
void _updateMapPosition(LatLng position, {double? zoom}) {
|
||||
_mapController.move(
|
||||
position,
|
||||
zoom ?? _mapController.camera.zoom,
|
||||
);
|
||||
|
||||
// Mettre à jour les variables d'état
|
||||
setState(() {
|
||||
_currentPosition = position;
|
||||
if (zoom != null) {
|
||||
_currentZoom = zoom;
|
||||
}
|
||||
});
|
||||
|
||||
// Sauvegarder les paramètres après mise à jour de la position
|
||||
_saveSettings();
|
||||
}
|
||||
|
||||
// Méthode pour construire les marqueurs des passages
|
||||
List<Marker> _buildMarkers() {
|
||||
if (_passages.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return _passages.map((passage) {
|
||||
final int passageType = passage['type'] as int;
|
||||
final Color color1 =
|
||||
passage['color'] as Color; // couleur1 du type de passage
|
||||
|
||||
// Récupérer la couleur2 du type de passage
|
||||
Color color2 = Colors.white; // Couleur par défaut
|
||||
if (AppKeys.typesPassages.containsKey(passageType)) {
|
||||
final colorValue =
|
||||
AppKeys.typesPassages[passageType]!['couleur2'] as int;
|
||||
color2 = Color(colorValue);
|
||||
}
|
||||
|
||||
return Marker(
|
||||
point: passage['position'] as LatLng,
|
||||
width: 14.0,
|
||||
height: 14.0,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
_showPassageInfo(passage);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: color1,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: color2,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
// Méthode pour construire les polygones des secteurs
|
||||
List<Polygon> _buildPolygons() {
|
||||
if (_sectors.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return _sectors.map((sector) {
|
||||
final bool isSelected = _selectedSectorId == sector['id'];
|
||||
final Color sectorColor = sector['color'] as Color;
|
||||
|
||||
return Polygon(
|
||||
points: sector['points'] as List<LatLng>,
|
||||
color: isSelected
|
||||
? sectorColor.withOpacity(0.5)
|
||||
: sectorColor.withOpacity(0.3),
|
||||
borderColor: isSelected ? sectorColor : sectorColor.withOpacity(0.8),
|
||||
borderStrokeWidth: isSelected ? 3.0 : 2.0,
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
// Afficher les informations d'un passage lorsqu'on clique dessus
|
||||
void _showPassageInfo(Map<String, dynamic> passage) {
|
||||
final PassageModel passageModel = passage['model'] as PassageModel;
|
||||
final int type = passageModel.fkType;
|
||||
|
||||
// Construire l'adresse complète
|
||||
final String adresse =
|
||||
'${passageModel.numero}, ${passageModel.rueBis} ${passageModel.rue}';
|
||||
|
||||
// Informations sur l'étage, l'appartement et la résidence (si habitat = 2)
|
||||
String? etageInfo;
|
||||
String? apptInfo;
|
||||
String? residenceInfo;
|
||||
if (passageModel.fkHabitat == 2) {
|
||||
if (passageModel.niveau.isNotEmpty) {
|
||||
etageInfo = 'Etage ${passageModel.niveau}';
|
||||
}
|
||||
if (passageModel.appt.isNotEmpty) {
|
||||
apptInfo = 'appt. ${passageModel.appt}';
|
||||
}
|
||||
if (passageModel.residence.isNotEmpty) {
|
||||
residenceInfo = passageModel.residence;
|
||||
}
|
||||
}
|
||||
|
||||
// Formater la date (uniquement si le type n'est pas 2)
|
||||
String dateInfo = '';
|
||||
if (type != 2) {
|
||||
dateInfo = 'Date: ${_formatDate(passageModel.passedAt)}';
|
||||
}
|
||||
|
||||
// Récupérer le nom du passage (si le type n'est pas 6 - Maison vide)
|
||||
String? nomInfo;
|
||||
if (type != 6 && passageModel.name.isNotEmpty) {
|
||||
nomInfo = passageModel.name;
|
||||
}
|
||||
|
||||
// Récupérer les informations de règlement si le type est 1 (Effectué) ou 5 (Lot)
|
||||
Widget? reglementInfo;
|
||||
if (type == 1 || type == 5) {
|
||||
final int typeReglementId = passageModel.fkTypeReglement;
|
||||
final String montant = passageModel.montant;
|
||||
|
||||
// Récupérer les informations du type de règlement
|
||||
if (AppKeys.typesReglements.containsKey(typeReglementId)) {
|
||||
final Map<String, dynamic> typeReglement =
|
||||
AppKeys.typesReglements[typeReglementId]!;
|
||||
final String titre = typeReglement['titre'] as String;
|
||||
final Color couleur = Color(typeReglement['couleur'] as int);
|
||||
final IconData iconData = typeReglement['icon_data'] as IconData;
|
||||
|
||||
reglementInfo = Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(iconData, color: couleur, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Text('$titre: $montant €',
|
||||
style:
|
||||
TextStyle(color: couleur, fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Afficher une bulle d'information
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 0),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Adresse: $adresse'),
|
||||
if (residenceInfo != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(residenceInfo)
|
||||
],
|
||||
if (etageInfo != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(etageInfo)
|
||||
],
|
||||
if (apptInfo != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(apptInfo)
|
||||
],
|
||||
if (dateInfo.isNotEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(dateInfo)
|
||||
],
|
||||
if (nomInfo != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text('Nom: $nomInfo')
|
||||
],
|
||||
if (reglementInfo != null) reglementInfo,
|
||||
],
|
||||
),
|
||||
actionsPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
// Bouton d'édition
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
// Logique pour éditer le passage
|
||||
debugPrint('Éditer le passage ${passageModel.id}');
|
||||
},
|
||||
icon: const Icon(Icons.edit),
|
||||
color: Colors.blue,
|
||||
tooltip: 'Modifier',
|
||||
),
|
||||
|
||||
// Bouton de suppression
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
// Logique pour supprimer le passage
|
||||
debugPrint('Supprimer le passage ${passageModel.id}');
|
||||
},
|
||||
icon: const Icon(Icons.delete),
|
||||
color: Colors.red,
|
||||
tooltip: 'Supprimer',
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Bouton de fermeture
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Fermer'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Formater une date
|
||||
String _formatDate(DateTime date) {
|
||||
return '${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year}';
|
||||
}
|
||||
|
||||
// Widget pour les boutons d'action
|
||||
Widget _buildActionButton({
|
||||
required IconData icon,
|
||||
required String tooltip,
|
||||
required VoidCallback? onPressed,
|
||||
Color color = Colors.blue,
|
||||
}) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: FloatingActionButton(
|
||||
heroTag: tooltip, // Nécessaire pour éviter les conflits de hero tags
|
||||
onPressed: onPressed,
|
||||
backgroundColor: onPressed != null ? color : Colors.grey,
|
||||
tooltip: tooltip,
|
||||
mini: true,
|
||||
child: Icon(icon),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
// Carte MapBox
|
||||
MapboxMap(
|
||||
initialPosition: _currentPosition,
|
||||
initialZoom: _currentZoom,
|
||||
mapController: _mapController,
|
||||
markers: _buildMarkers(),
|
||||
polygons: _buildPolygons(),
|
||||
showControls: true,
|
||||
onMapEvent: (event) {
|
||||
if (event is MapEventMove) {
|
||||
setState(() {
|
||||
_currentPosition = event.camera.center;
|
||||
_currentZoom = event.camera.zoom;
|
||||
});
|
||||
_saveSettings();
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
// Bouton Mode édition en haut à droite
|
||||
Positioned(
|
||||
right: 16,
|
||||
top: 16,
|
||||
child: _buildActionButton(
|
||||
icon: Icons.edit,
|
||||
tooltip: 'Mode édition',
|
||||
color: _editMode ? Colors.green : Colors.blue,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_editMode = !_editMode;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// Boutons d'action sous le bouton Mode édition
|
||||
Positioned(
|
||||
right: 16,
|
||||
top: 80, // Positionner sous le bouton Mode édition
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
if (_editMode) ...[
|
||||
_buildActionButton(
|
||||
icon: Icons.add,
|
||||
tooltip: 'Ajouter un secteur',
|
||||
onPressed: () {
|
||||
// Action pour ajouter un secteur
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildActionButton(
|
||||
icon: Icons.edit,
|
||||
tooltip: 'Modifier le secteur sélectionné',
|
||||
onPressed: _selectedSectorId != null
|
||||
? () {
|
||||
// Action pour modifier le secteur sélectionné
|
||||
}
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildActionButton(
|
||||
icon: Icons.delete,
|
||||
tooltip: 'Supprimer le secteur sélectionné',
|
||||
color: Colors.red,
|
||||
onPressed: _selectedSectorId != null
|
||||
? () {
|
||||
// Action pour supprimer le secteur sélectionné
|
||||
}
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Bouton Ma position en bas à droite
|
||||
Positioned(
|
||||
right: 16,
|
||||
bottom: 16,
|
||||
child: _buildActionButton(
|
||||
icon: Icons.my_location,
|
||||
tooltip: 'Ma position',
|
||||
onPressed: () {
|
||||
_getUserLocation();
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// Combobox de sélection de secteurs
|
||||
Positioned(
|
||||
left: 16,
|
||||
top: 16,
|
||||
child: Material(
|
||||
elevation: 4,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
width: 220, // Largeur fixe pour accommoder les noms longs
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.95),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.location_on, size: 18, color: Colors.blue),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: DropdownButton<int?>(
|
||||
value: _selectedSectorId,
|
||||
hint: const Text('Tous les secteurs'),
|
||||
isExpanded: true,
|
||||
underline: Container(), // Supprimer la ligne sous le dropdown
|
||||
icon: Icon(Icons.arrow_drop_down, color: Colors.blue),
|
||||
items: _sectorItems,
|
||||
onChanged: (int? sectorId) {
|
||||
setState(() {
|
||||
_selectedSectorId = sectorId;
|
||||
});
|
||||
|
||||
if (sectorId != null) {
|
||||
_centerMapOnSpecificSector(sectorId);
|
||||
} else {
|
||||
// Si "Tous les secteurs" est sélectionné
|
||||
_centerMapOnSectors();
|
||||
// Recharger tous les passages sans filtrage par secteur
|
||||
_loadPassages();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user