feat: Version 3.5.2 - Configuration Stripe et gestion des immeubles

- Configuration complète Stripe pour les 3 environnements (DEV/REC/PROD)
  * DEV: Clés TEST Pierre (mode test)
  * REC: Clés TEST Client (mode test)
  * PROD: Clés LIVE Client (mode live)
- Ajout de la gestion des bases de données immeubles/bâtiments
  * Configuration buildings_database pour DEV/REC/PROD
  * Service BuildingService pour enrichissement des adresses
- Optimisations pages et améliorations ergonomie
- Mises à jour des dépendances Composer
- Nettoyage des fichiers obsolètes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
pierre
2025-11-09 18:26:27 +01:00
parent 21657a3820
commit 2f5946a184
812 changed files with 142105 additions and 25992 deletions

View File

@@ -20,6 +20,7 @@ import 'package:geosector_app/core/services/current_amicale_service.dart';
import 'package:geosector_app/core/services/current_user_service.dart';
import 'package:geosector_app/core/repositories/operation_repository.dart';
import 'package:geosector_app/presentation/widgets/passage_map_dialog.dart';
import 'package:geosector_app/presentation/widgets/grouped_passages_dialog.dart';
import 'package:go_router/go_router.dart';
/// Page de carte globale pour admin et utilisateurs
@@ -29,6 +30,7 @@ class MapPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
debugPrint('🔄 MapPage.build() appelé');
// Utiliser le mode d'affichage au lieu du rôle réel
final isAdmin = CurrentUserService.instance.shouldShowAdminUI;
@@ -78,6 +80,9 @@ class _MapPageContentState extends State<MapPageContent> {
bool _showZoomIndicator = false;
Timer? _zoomIndicatorTimer;
// Timer pour debouncer le setState et la sauvegarde lors du déplacement de carte
Timer? _mapMoveDebounceTimer;
// États
MapMode _mapMode = MapMode.view;
int? _selectedSectorId;
@@ -102,7 +107,7 @@ class _MapPageContentState extends State<MapPageContent> {
// États pour le mode édition
SectorModel? _selectedSectorForEdit;
List<LatLng> _editingPoints = [];
Map<int, LatLng> _originalPoints = {}; // Pour annuler les modifications
final Map<int, LatLng> _originalPoints = {}; // Pour annuler les modifications
int? _hoveredPointIndex; // Index du point principal survolé
// État pour le mode suppression
@@ -115,6 +120,9 @@ class _MapPageContentState extends State<MapPageContent> {
// État pour bloquer le drag de la carte pendant le déplacement des points
bool _isDraggingPoint = false;
// État pour bloquer la sauvegarde du zoom lors du centrage sur secteur
bool _isCenteringOnSector = false;
// Comptages des secteurs (calculés uniquement lors de création/modification de secteurs)
Map<int, int> _sectorPassageCount = {};
Map<int, int> _sectorMemberCount = {};
@@ -143,6 +151,14 @@ class _MapPageContentState extends State<MapPageContent> {
// Écouter les changements du secteur sélectionné
_settingsListenable = _settingsBox.listenable(keys: ['selectedSectorId']);
_settingsListenable.addListener(_onSectorSelectionChanged);
// Centrer sur le secteur si déjà sélectionné (navigation depuis home_page)
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_selectedSectorId != null && _sectors.any((s) => s['id'] == _selectedSectorId)) {
debugPrint('🎯 MapPage: Secteur présélectionné détecté ($_selectedSectorId), centrage...');
_centerMapOnSpecificSector(_selectedSectorId!);
}
});
});
}
@@ -225,6 +241,7 @@ class _MapPageContentState extends State<MapPageContent> {
void dispose() {
_settingsListenable.removeListener(_onSectorSelectionChanged);
_zoomIndicatorTimer?.cancel();
_mapMoveDebounceTimer?.cancel();
_mapController.dispose();
super.dispose();
}
@@ -258,10 +275,14 @@ class _MapPageContentState extends State<MapPageContent> {
_settingsBox.put('selectedSectorId', _selectedSectorId);
}
// Sauvegarder la position et le zoom actuels
// Sauvegarder la position
_settingsBox.put('mapLat', _currentPosition.latitude);
_settingsBox.put('mapLng', _currentPosition.longitude);
_settingsBox.put('mapZoom', _currentZoom);
// Sauvegarder le zoom SAUF si on est en train de centrer sur un secteur
if (!_isCenteringOnSector) {
_settingsBox.put('mapZoom', _currentZoom);
}
}
// Mettre à jour les comptages des secteurs (passages et membres)
@@ -380,7 +401,8 @@ class _MapPageContentState extends State<MapPageContent> {
}
}
if (mounted) {
// Ne faire setState QUE si les données ont vraiment changé
if (mounted && !_arePassagesEqual(_passages, newPassages)) {
setState(() {
_passages.clear();
_passages.addAll(newPassages);
@@ -391,6 +413,25 @@ class _MapPageContentState extends State<MapPageContent> {
}
}
// Comparer deux listes de passages pour éviter les setState inutiles
bool _arePassagesEqual(List<Map<String, dynamic>> oldPassages, List<Map<String, dynamic>> newPassages) {
if (oldPassages.length != newPassages.length) return false;
// Créer des clés uniques incluant ID + fkType pour détecter les changements de type
// (important pour le gradient des immeubles qui dépend du fkType)
final oldKeys = oldPassages.map((p) {
final model = p['model'] as PassageModel;
return '${model.id}_${model.fkType}';
}).toSet();
final newKeys = newPassages.map((p) {
final model = p['model'] as PassageModel;
return '${model.id}_${model.fkType}';
}).toSet();
return oldKeys.length == newKeys.length && oldKeys.containsAll(newKeys);
}
// Charger les passages depuis la boîte Hive (avec setState)
void _loadPassages() {
// L'API retourne déjà les passages filtrés selon le rôle (admin ou user)
@@ -640,21 +681,30 @@ class _MapPageContentState extends State<MapPageContent> {
final centerLat = (minLat + maxLat) / 2;
final centerLng = (minLng + maxLng) / 2;
// Lire le zoom actuel de la caméra pour le conserver exactement
final currentZoom = _mapController.camera.zoom;
// CAPTURER le zoom actuel AVANT toute opération pour le conserver
final preservedZoom = _currentZoom;
// Centrer la carte sur le secteur SANS changer le zoom
debugPrint('🔍 MapPage: Centrage sur secteur (zoom conservé: $currentZoom)');
_mapController.move(LatLng(centerLat, centerLng), currentZoom);
// ACTIVER le flag pour bloquer la sauvegarde du zoom
_isCenteringOnSector = true;
// Mettre à jour uniquement la position (pas le zoom)
// Centrer la carte sur le secteur en FORCANT le zoom actuel
debugPrint('🔍 MapPage: Centrage sur secteur (zoom FORCÉ à conserver: $preservedZoom)');
_mapController.move(LatLng(centerLat, centerLng), preservedZoom);
// Mettre à jour UNIQUEMENT la position, PAS le zoom
setState(() {
_currentPosition = LatLng(centerLat, centerLng);
// On ne touche PAS à _currentZoom !
});
// Sauvegarder la nouvelle position
// Sauvegarder la nouvelle position (le zoom ne sera pas sauvegardé grâce au flag)
_saveSettings();
// DÉSACTIVER le flag après un court délai pour permettre les sauvegardes normales
Future.delayed(const Duration(milliseconds: 100), () {
_isCenteringOnSector = false;
});
// Recharger les passages pour appliquer le filtre par secteur
_loadPassages();
}
@@ -765,10 +815,10 @@ class _MapPageContentState extends State<MapPageContent> {
Set<int>? userSectorIds;
if (!isAdmin) {
final userSectorBox = Hive.box<UserSectorModel>(AppKeys.userSectorBoxName);
final currentUserId = CurrentUserService.instance.currentUser?.id;
if (currentUserId != null) {
final currentOpeUserId = CurrentUserService.instance.opeUserId;
if (currentOpeUserId != null) {
userSectorIds = userSectorBox.values
.where((us) => us.id == currentUserId)
.where((us) => us.opeUserId == currentOpeUserId)
.map((us) => us.fkSector)
.toSet();
}
@@ -824,6 +874,7 @@ class _MapPageContentState extends State<MapPageContent> {
// Construire les marqueurs de labels pour les secteurs
List<Marker> _buildSectorLabels() {
debugPrint('🔄 _buildSectorLabels() appelé - ${_sectors.length} secteurs');
// Ne pas afficher les labels en mode dessin ou suppression
if (_sectors.isEmpty || _mapMode != MapMode.view) {
return [];
@@ -859,24 +910,9 @@ class _MapPageContentState extends State<MapPageContent> {
fontSize: 14,
shadows: [
Shadow(
color: Colors.white.withValues(alpha: 0.8),
offset: const Offset(1, 1),
blurRadius: 3,
),
Shadow(
color: Colors.white.withValues(alpha: 0.8),
offset: const Offset(-1, -1),
blurRadius: 3,
),
Shadow(
color: Colors.white.withValues(alpha: 0.8),
offset: const Offset(1, -1),
blurRadius: 3,
),
Shadow(
color: Colors.white.withValues(alpha: 0.8),
offset: const Offset(-1, 1),
blurRadius: 3,
color: Colors.white.withOpacity(0.95),
offset: const Offset(0, 0),
blurRadius: 6,
),
],
),
@@ -892,24 +928,9 @@ class _MapPageContentState extends State<MapPageContent> {
fontWeight: FontWeight.w600,
shadows: [
Shadow(
color: Colors.white.withValues(alpha: 0.8),
offset: const Offset(1, 1),
blurRadius: 3,
),
Shadow(
color: Colors.white.withValues(alpha: 0.8),
offset: const Offset(-1, -1),
blurRadius: 3,
),
Shadow(
color: Colors.white.withValues(alpha: 0.8),
offset: const Offset(1, -1),
blurRadius: 3,
),
Shadow(
color: Colors.white.withValues(alpha: 0.8),
offset: const Offset(-1, 1),
blurRadius: 3,
color: Colors.white.withOpacity(0.95),
offset: const Offset(0, 0),
blurRadius: 5,
),
],
),
@@ -923,24 +944,9 @@ class _MapPageContentState extends State<MapPageContent> {
fontWeight: FontWeight.w500,
shadows: [
Shadow(
color: Colors.white.withValues(alpha: 0.8),
offset: const Offset(1, 1),
blurRadius: 3,
),
Shadow(
color: Colors.white.withValues(alpha: 0.8),
offset: const Offset(-1, -1),
blurRadius: 3,
),
Shadow(
color: Colors.white.withValues(alpha: 0.8),
offset: const Offset(1, -1),
blurRadius: 3,
),
Shadow(
color: Colors.white.withValues(alpha: 0.8),
offset: const Offset(-1, 1),
blurRadius: 3,
color: Colors.white.withOpacity(0.95),
offset: const Offset(0, 0),
blurRadius: 5,
),
],
),
@@ -954,20 +960,60 @@ class _MapPageContentState extends State<MapPageContent> {
}
// Méthode pour construire les marqueurs des passages
/// Groupe les passages par adresse (pour fkHabitat=2)
/// Clé: numero+rueBis+rue+ville
Map<String, List<Map<String, dynamic>>> _groupPassagesByAddress() {
final Map<String, List<Map<String, dynamic>>> grouped = {};
for (final passage in _passages) {
final PassageModel model = passage['model'] as PassageModel;
// Ne grouper que les passages avec fkHabitat=2
if (model.fkHabitat == 2) {
final key = '${model.numero}|${model.rueBis}|${model.rue}|${model.ville}';
grouped.putIfAbsent(key, () => []);
grouped[key]!.add(passage);
}
}
return grouped;
}
List<Marker> _buildMarkers() {
debugPrint('🔄 _buildMarkers() appelé - ${_passages.length} passages');
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
final List<Marker> markers = [];
// 1. Grouper les passages fkHabitat=2 par adresse
final groupedPassages = _groupPassagesByAddress();
final Set<int> groupedPassageIds = {};
// Collecter les IDs des passages groupés
for (final group in groupedPassages.values) {
for (final passage in group) {
final PassageModel model = passage['model'] as PassageModel;
groupedPassageIds.add(model.id);
}
}
// 2. Créer les markers pour passages individuels (fkHabitat=1 ou non groupés)
for (final passage in _passages) {
final PassageModel passageModel = passage['model'] as PassageModel;
// Ignorer les passages déjà groupés
if (groupedPassageIds.contains(passageModel.id)) {
continue;
}
final int passageType = passage['type'] as int;
final Color color1 = passage['color'] as Color;
final bool hasNoSector = passageModel.fkSector == null;
// Récupérer la couleur2 du type de passage
Color color2 = Colors.white; // Couleur par défaut
Color color2 = Colors.white;
if (AppKeys.typesPassages.containsKey(passageType)) {
final colorValue =
AppKeys.typesPassages[passageType]!['couleur2'] as int;
@@ -978,38 +1024,112 @@ class _MapPageContentState extends State<MapPageContent> {
final Color borderColor = hasNoSector ? Colors.red : color2;
final double borderWidth = hasNoSector ? 3.0 : 1.0;
return Marker(
point: passage['position'] as LatLng,
width: hasNoSector ? 18.0 : 14.0, // Plus grand si orphelin
height: hasNoSector ? 18.0 : 14.0,
child: GestureDetector(
onTap: () {
_showPassageInfo(passage);
},
child: Container(
decoration: BoxDecoration(
color: color1,
shape: BoxShape.circle,
border: Border.all(
color: borderColor,
width: borderWidth,
markers.add(
Marker(
point: passage['position'] as LatLng,
width: hasNoSector ? 18.0 : 14.0,
height: hasNoSector ? 18.0 : 14.0,
child: GestureDetector(
onTap: () {
_showPassageInfo(passage);
},
child: Container(
decoration: BoxDecoration(
color: color1,
shape: BoxShape.circle,
border: Border.all(
color: borderColor,
width: borderWidth,
),
),
),
),
),
);
}).toList();
}
// 3. Créer les markers groupés (carrés avec dégradé blanc→vert selon avancement)
for (final entry in groupedPassages.entries) {
final passages = entry.value;
if (passages.isEmpty) continue;
// Utiliser la position du premier passage du groupe
final position = passages.first['position'] as LatLng;
final count = passages.length;
final displayCount = count >= 99 ? '99' : count.toString();
// Calculer le pourcentage de passages réalisés (fkType != 2)
final models = passages.map((p) => p['model'] as PassageModel).toList();
final realizedCount = models.where((p) => p.fkType != 2).length;
final percentage = realizedCount / models.length;
// Déterminer la couleur de remplissage selon le palier (5 niveaux)
Color fillColor;
if (percentage == 0) {
// 0% : Blanc pur
fillColor = Colors.white;
} else if (percentage <= 0.25) {
// 1-25% : Blanc cassé → Vert très clair
fillColor = Color.lerp(Colors.white, const Color(0xFFB3F5E0), (percentage / 0.25))!;
} else if (percentage <= 0.50) {
// 26-50% : Vert très clair → Vert clair
fillColor = Color.lerp(const Color(0xFFB3F5E0), const Color(0xFF66EBBB), ((percentage - 0.25) / 0.25))!;
} else if (percentage <= 0.75) {
// 51-75% : Vert clair → Vert moyen
fillColor = Color.lerp(const Color(0xFF66EBBB), const Color(0xFF33E1A0), ((percentage - 0.50) / 0.25))!;
} else if (percentage < 1.0) {
// 76-99% : Vert moyen → Vert foncé
fillColor = Color.lerp(const Color(0xFF33E1A0), const Color(0xFF00E09D), ((percentage - 0.75) / 0.25))!;
} else {
// 100% : Vert foncé (couleur "Effectué")
fillColor = const Color(0xFF00E09D);
}
markers.add(
Marker(
point: position,
width: 24.0,
height: 24.0,
child: GestureDetector(
onTap: () {
_showGroupedPassagesDialog(passages.first['model'] as PassageModel);
},
child: Container(
decoration: BoxDecoration(
color: fillColor,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: Colors.blue, // Bordure bleue toujours
width: 2,
),
),
child: Center(
child: Text(
displayCount,
style: TextStyle(
color: percentage < 0.5 ? Colors.black87 : Colors.white, // Texte noir sur fond clair, blanc sur fond foncé
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
),
),
),
);
}
return markers;
}
// Méthode pour construire les polygones des secteurs
List<Polygon> _buildPolygons() {
debugPrint('🔄 _buildPolygons() appelé - ${_sectors.length} secteurs');
if (_sectors.isEmpty) {
debugPrint('MapPage: Aucun secteur à afficher');
return [];
}
debugPrint('MapPage: Construction de ${_sectors.length} polygones');
return _sectors.map((sector) {
final int sectorId = sector['id'] as int;
final bool isSelected = _selectedSectorId == sectorId;
@@ -1024,8 +1144,6 @@ class _MapPageContentState extends State<MapPageContent> {
_mapMode == MapMode.editing && _selectedSectorForEdit?.id == sectorId;
final Color sectorColor = sector['color'] as Color;
debugPrint('MapPage: Secteur ${sector['name']} - Couleur: $sectorColor');
// Déterminer la couleur et l'opacité selon l'état
Color fillColor;
Color borderColor;
@@ -1033,33 +1151,33 @@ class _MapPageContentState extends State<MapPageContent> {
if (isMarkedForDeletion) {
// Secteur marqué pour suppression
fillColor = Colors.red.withValues(alpha: 0.5);
fillColor = Colors.red.withOpacity(0.5);
borderColor = Colors.red;
borderWidth = 4.0;
} else if (isHovered) {
// Secteur survolé en mode suppression
fillColor = sectorColor.withValues(alpha: 0.45);
borderColor = Colors.red.withValues(alpha: 0.8);
fillColor = sectorColor.withOpacity(0.45);
borderColor = Colors.red.withOpacity(0.8);
borderWidth = 3.0;
} else if (isHoveredForEdit) {
// Secteur survolé en mode édition
fillColor = sectorColor.withValues(alpha: 0.45);
fillColor = sectorColor.withOpacity(0.45);
borderColor = Colors.green;
borderWidth = 4.0;
} else if (isSelectedForEdit) {
// Secteur sélectionné pour édition
fillColor = sectorColor.withValues(alpha: 0.5);
fillColor = sectorColor.withOpacity(0.5);
borderColor = Colors.orange;
borderWidth = 4.0;
} else if (isSelected) {
// Secteur sélectionné
fillColor = sectorColor.withValues(alpha: 0.5);
fillColor = sectorColor.withOpacity(0.5);
borderColor = sectorColor;
borderWidth = 3.0;
} else {
// Secteur normal
fillColor = sectorColor.withValues(alpha: 0.3);
borderColor = sectorColor.withValues(alpha: 0.8);
fillColor = sectorColor.withOpacity(0.3);
borderColor = sectorColor.withOpacity(0.8);
borderWidth = 2.0;
}
@@ -1068,7 +1186,6 @@ class _MapPageContentState extends State<MapPageContent> {
color: fillColor,
borderColor: borderColor,
borderStrokeWidth: borderWidth,
isFilled: true, // IMPORTANT: Active le remplissage coloré
);
}).toList();
}
@@ -1090,13 +1207,31 @@ class _MapPageContentState extends State<MapPageContent> {
);
}
// Afficher le dialogue des passages groupés (immeuble)
void _showGroupedPassagesDialog(PassageModel referencePassage) {
showDialog(
context: context,
builder: (context) => GroupedPassagesDialog(
referencePassage: referencePassage,
isAdmin: isAdmin,
),
);
}
// Démarrer le mode dessin
void _startDrawingMode() {
if (!canEditSectors) return; // Vérifier les permissions
setState(() {
_mapMode = MapMode.drawing;
_drawingPoints.clear();
// Sélectionner automatiquement "Aucun passage"
_selectedPassageTypeFilter = null;
_settingsBox.put('selectedPassageTypeFilter', null);
});
// Recharger les passages avec le nouveau filtre
_loadPassages();
}
// Démarrer le mode suppression
@@ -1105,7 +1240,14 @@ class _MapPageContentState extends State<MapPageContent> {
setState(() {
_mapMode = MapMode.deleting;
_sectorToDeleteId = null;
// Sélectionner automatiquement "Aucun passage"
_selectedPassageTypeFilter = null;
_settingsBox.put('selectedPassageTypeFilter', null);
});
// Recharger les passages avec le nouveau filtre
_loadPassages();
}
// Démarrer le mode édition
@@ -1115,7 +1257,14 @@ class _MapPageContentState extends State<MapPageContent> {
_mapMode = MapMode.editing;
_selectedSectorForEdit = null;
_editingPoints.clear();
// Sélectionner automatiquement "Aucun passage"
_selectedPassageTypeFilter = null;
_settingsBox.put('selectedPassageTypeFilter', null);
});
// Recharger les passages avec le nouveau filtre
_loadPassages();
}
// Construire la carte d'aide pour le mode création
@@ -1127,10 +1276,10 @@ class _MapPageContentState extends State<MapPageContent> {
padding: const EdgeInsets.all(16),
width: 320,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.95),
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.blue.withValues(alpha: 0.3),
color: Colors.blue.withOpacity(0.3),
width: 1,
),
),
@@ -1263,10 +1412,10 @@ class _MapPageContentState extends State<MapPageContent> {
padding: const EdgeInsets.all(16),
width: 360,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.95),
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.red.withValues(alpha: 0.3),
color: Colors.red.withOpacity(0.3),
width: 1,
),
),
@@ -1332,10 +1481,10 @@ class _MapPageContentState extends State<MapPageContent> {
padding: const EdgeInsets.all(16),
width: 340,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.95),
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.orange.withValues(alpha: 0.3),
color: Colors.orange.withOpacity(0.3),
width: 1,
),
),
@@ -1384,10 +1533,10 @@ class _MapPageContentState extends State<MapPageContent> {
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.orange.withValues(alpha: 0.1),
color: Colors.orange.withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
border:
Border.all(color: Colors.orange.withValues(alpha: 0.3)),
Border.all(color: Colors.orange.withOpacity(0.3)),
),
child: Text(
'La modification est verrouillée sur ce secteur.\n'
@@ -2772,9 +2921,9 @@ class _MapPageContentState extends State<MapPageContent> {
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.orange.withValues(alpha: 0.1),
color: Colors.orange.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.orange.withValues(alpha: 0.3)),
border: Border.all(color: Colors.orange.withOpacity(0.3)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -2885,6 +3034,7 @@ class _MapPageContentState extends State<MapPageContent> {
// Recharger les secteurs et passages après la suppression
_loadSectors();
_loadPassages();
_updateSectorCounts(); // Rafraîchir le comptage des membres
// Message de succès simple
if (mounted) {
@@ -2965,7 +3115,7 @@ class _MapPageContentState extends State<MapPageContent> {
builder: (dialogContext) => SectorDialog(
existingSector: existingSector,
coordinates: finalCoordinates,
onSave: (name, color, memberIds) async {
onSave: (name, color, memberIds, updatePassages) async {
// Le dialog se ferme automatiquement dans _handleSave()
// Attendre un peu pour s'assurer que le dialog est fermé
await Future.delayed(const Duration(milliseconds: 100));
@@ -2998,10 +3148,9 @@ class _MapPageContentState extends State<MapPageContent> {
if (existingSector == null) {
// Création d'un nouveau secteur
// Convertir les coordonnées au format attendu par l'API : "lat/lng#lat/lng#..."
final sectorString = finalCoordinates
final sectorString = '${finalCoordinates
.map((coord) => '${coord[0]}/${coord[1]}')
.join('#') +
'#'; // Ajouter un # final comme dans les exemples
.join('#')}#'; // Ajouter un # final comme dans les exemples
final newSector = SectorModel(
id: 0, // L'API assignera l'ID
@@ -3059,12 +3208,26 @@ class _MapPageContentState extends State<MapPageContent> {
// Recharger les secteurs et passages
_loadSectors();
_loadPassages();
_updateSectorCounts(); // Rafraîchir le comptage des membres
// Centrer la carte sur le nouveau secteur
// Présélectionner le secteur créé et afficher tous ses passages
if (result.containsKey('sector') && result['sector'] != null) {
final newSector = result['sector'] as SectorModel;
// Attendre un peu que les données soient chargées
setState(() {
// Sélectionner le secteur créé
_selectedSectorId = newSector.id;
_settingsBox.put('selectedSectorId', newSector.id);
// Mettre le filtre sur "Tous les passages"
_selectedPassageTypeFilter = -1;
_settingsBox.put('selectedPassageTypeFilter', -1);
});
// Recharger les passages avec le nouveau filtre
_loadPassages();
// Centrer la carte sur le nouveau secteur
Future.delayed(const Duration(milliseconds: 500), () {
if (mounted) {
_centerMapOnSpecificSector(newSector.id);
@@ -3098,10 +3261,9 @@ class _MapPageContentState extends State<MapPageContent> {
}
} else {
// Modification d'un secteur existant
final sectorString = finalCoordinates
final sectorString = '${finalCoordinates
.map((coord) => '${coord[0]}/${coord[1]}')
.join('#') +
'#';
.join('#')}#';
final updatedSector = existingSector.copyWith(
libelle: name,
@@ -3109,8 +3271,11 @@ class _MapPageContentState extends State<MapPageContent> {
sector: sectorString,
);
result = await sectorRepository.updateSector(updatedSector,
users: memberIds);
result = await sectorRepository.updateSector(
updatedSector,
users: memberIds,
chkAdressesChange: updatePassages ? 1 : 0,
);
if (result['status'] != 'success') {
throw Exception(result['message'] ??
@@ -3131,8 +3296,29 @@ class _MapPageContentState extends State<MapPageContent> {
// Recharger les secteurs et passages
_loadSectors();
_updateSectorCounts(); // Rafraîchir le comptage des membres
// Présélectionner le secteur modifié et afficher tous ses passages
setState(() {
// Sélectionner le secteur modifié
_selectedSectorId = existingSector.id;
_settingsBox.put('selectedSectorId', existingSector.id);
// Mettre le filtre sur "Tous les passages"
_selectedPassageTypeFilter = -1;
_settingsBox.put('selectedPassageTypeFilter', -1);
});
// Recharger les passages avec le nouveau filtre
_loadPassages();
// Centrer la carte sur le secteur modifié
Future.delayed(const Duration(milliseconds: 500), () {
if (mounted) {
_centerMapOnSpecificSector(existingSector.id);
}
});
if (parentContext.mounted) {
ScaffoldMessenger.of(parentContext).hideCurrentSnackBar();
}
@@ -3206,7 +3392,7 @@ class _MapPageContentState extends State<MapPageContent> {
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.2),
color: Colors.black.withOpacity(0.2),
blurRadius: 4,
offset: const Offset(0, 2),
),
@@ -3316,7 +3502,7 @@ class _MapPageContentState extends State<MapPageContent> {
Polyline(
points: _drawingPoints,
strokeWidth: 3.0,
color: Colors.blue.withValues(alpha: 0.8),
color: Colors.blue.withOpacity(0.8),
),
);
}
@@ -3330,7 +3516,7 @@ class _MapPageContentState extends State<MapPageContent> {
_editingPoints.first
], // Fermer le polygone
strokeWidth: 3.0,
color: Colors.orange.withValues(alpha: 0.8),
color: Colors.orange.withOpacity(0.8),
),
);
}
@@ -3450,7 +3636,7 @@ class _MapPageContentState extends State<MapPageContent> {
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.3),
color: Colors.black.withOpacity(0.3),
blurRadius: _draggingPointIndex == i ? 6 : 4,
offset: const Offset(0, 2),
),
@@ -3520,8 +3706,8 @@ class _MapPageContentState extends State<MapPageContent> {
duration: const Duration(milliseconds: 200),
decoration: BoxDecoration(
color: _hoveredMidpointIndex == i
? Colors.blue.withValues(alpha: 0.8)
: Colors.grey.withValues(alpha: 0.5),
? Colors.blue.withOpacity(0.8)
: Colors.grey.withOpacity(0.5),
shape: BoxShape.circle,
border: Border.all(
color:
@@ -3666,7 +3852,7 @@ class _MapPageContentState extends State<MapPageContent> {
height: 20.0,
child: Container(
decoration: BoxDecoration(
color: Colors.orange.withValues(alpha: 0.5),
color: Colors.orange.withOpacity(0.5),
shape: BoxShape.circle,
border: Border.all(
color: Colors.orange,
@@ -3674,7 +3860,7 @@ class _MapPageContentState extends State<MapPageContent> {
),
boxShadow: [
BoxShadow(
color: Colors.orange.withValues(alpha: 0.5),
color: Colors.orange.withOpacity(0.5),
blurRadius: 8,
spreadRadius: 2,
),
@@ -3825,13 +4011,13 @@ class _MapPageContentState extends State<MapPageContent> {
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.3),
color: Colors.black.withOpacity(0.3),
blurRadius: isDragging ? 8 : (isHovered ? 6 : 4),
offset: const Offset(0, 2),
),
if (isHovered && !isDragging)
BoxShadow(
color: Colors.orange.withValues(alpha: 0.3),
color: Colors.orange.withOpacity(0.3),
blurRadius: 15,
spreadRadius: 2,
),
@@ -3889,8 +4075,8 @@ class _MapPageContentState extends State<MapPageContent> {
duration: const Duration(milliseconds: 200),
decoration: BoxDecoration(
color: _hoveredMidpointIndex == i
? Colors.orange.withValues(alpha: 0.8)
: Colors.grey.withValues(alpha: 0.5),
? Colors.orange.withOpacity(0.8)
: Colors.grey.withOpacity(0.5),
shape: BoxShape.circle,
border: Border.all(
color: _hoveredMidpointIndex == i
@@ -3980,24 +4166,29 @@ class _MapPageContentState extends State<MapPageContent> {
onMapEvent: (event) {
if (event is MapEventMove) {
final displayedZoom = event.camera.zoom;
debugPrint('🔍 MapPage: Zoom affiché par la caméra = $displayedZoom (précédent _currentZoom = $_currentZoom)');
// Afficher l'indicateur de zoom si le niveau a changé
if ((displayedZoom - _currentZoom).abs() > 0.01) {
_showZoomIndicatorTemporarily();
}
setState(() {
_currentPosition = event.camera.center;
_currentZoom = displayedZoom;
// Mettre à jour les variables sans setState() immédiat
_currentPosition = event.camera.center;
_currentZoom = displayedZoom;
// Annuler le timer précédent
_mapMoveDebounceTimer?.cancel();
// Lancer un nouveau timer de 300ms pour debouncer
_mapMoveDebounceTimer = Timer(const Duration(milliseconds: 300), () {
if (mounted) {
// setState uniquement après 300ms sans mouvement
setState(() {
// Les variables sont déjà à jour
});
_saveSettings();
}
});
_saveSettings();
// Mettre à jour le survol après un mouvement de carte
if (_mapMode == MapMode.deleting && kIsWeb) {
// On doit recalculer car la carte a bougé
// Note: On ne peut pas obtenir la position de la souris ici,
// elle sera mise à jour au prochain mouvement de souris
}
} else if (event is MapEventTap &&
(_mapMode == MapMode.drawing ||
_mapMode == MapMode.deleting ||
@@ -4091,7 +4282,7 @@ class _MapPageContentState extends State<MapPageContent> {
horizontal: 12, vertical: 4),
width: 220, // Largeur fixe pour accommoder les noms longs
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.95),
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(8),
),
child: Row(
@@ -4152,7 +4343,7 @@ class _MapPageContentState extends State<MapPageContent> {
horizontal: 12, vertical: 4),
width: 220,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.95),
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(8),
),
child: Row(
@@ -4229,7 +4420,7 @@ class _MapPageContentState extends State<MapPageContent> {
vertical: 8,
),
decoration: BoxDecoration(
color: Colors.blue.withValues(alpha: 0.3),
color: Colors.blue.withOpacity(0.3),
borderRadius: BorderRadius.circular(20),
),
child: Text(