Files
geo/app/lib/presentation/widgets/mapbox_map.dart
pierre 2f5946a184 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>
2025-11-09 18:26:27 +01:00

354 lines
11 KiB
Dart
Executable File

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_cache/flutter_map_cache.dart';
import 'package:dio_cache_interceptor_hive_store/dio_cache_interceptor_hive_store.dart';
import 'package:path_provider/path_provider.dart';
import 'package:latlong2/latlong.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
import 'package:geosector_app/core/services/api_service.dart'; // Import du service singleton
/// Widget de carte réutilisable utilisant Mapbox
///
/// Ce widget encapsule un FlutterMap avec des tuiles Mapbox et fournit
/// des fonctionnalités pour afficher des marqueurs, des polygones et des contrôles.
class MapboxMap extends StatefulWidget {
/// Position initiale de la carte
final LatLng initialPosition;
/// Niveau de zoom initial
final double initialZoom;
/// Liste des marqueurs à afficher (au-dessus de tout)
final List<Marker>? markers;
/// Liste des marqueurs de labels à afficher (sous les marqueurs principaux)
final List<Marker>? labelMarkers;
/// Liste des polygones à afficher
final List<Polygon>? polygons;
/// Liste des polylines à afficher
final List<Polyline>? polylines;
/// Contrôleur de carte externe (optionnel)
final MapController? mapController;
/// Callback appelé lorsque la carte est déplacée
final void Function(MapEvent)? onMapEvent;
/// Afficher les boutons de contrôle (zoom, localisation)
final bool showControls;
/// Style de la carte Mapbox (optionnel)
/// Si non spécifié, utilise le style par défaut 'mapbox/streets-v12'
final String? mapStyle;
/// Désactive le drag de la carte
final bool disableDrag;
/// Utiliser OpenStreetMap au lieu de Mapbox (en cas de problème de token)
final bool useOpenStreetMap;
const MapboxMap({
super.key,
this.initialPosition = const LatLng(48.1173, -1.6778), // Rennes par défaut
this.initialZoom = 13.0,
this.markers,
this.labelMarkers,
this.polygons,
this.polylines,
this.mapController,
this.onMapEvent,
this.showControls = true,
this.mapStyle,
this.disableDrag = false,
this.useOpenStreetMap = false,
});
@override
State<MapboxMap> createState() => _MapboxMapState();
}
class _MapboxMapState extends State<MapboxMap> {
/// Contrôleur de carte interne
late final MapController _mapController;
/// Niveau de zoom actuel (utilisé pour l'affichage futur)
// ignore: unused_field
double _currentZoom = 13.0;
/// Provider de tuiles (peut être NetworkTileProvider ou CachedTileProvider)
TileProvider? _tileProvider;
/// Indique si le cache est initialisé
bool _cacheInitialized = false;
@override
void initState() {
super.initState();
_mapController = widget.mapController ?? MapController();
_currentZoom = widget.initialZoom;
_initializeCache();
}
/// Initialise le cache des tuiles
Future<void> _initializeCache() async {
try {
if (kIsWeb) {
// Pas de cache sur Web (non supporté)
setState(() {
_cacheInitialized = true;
});
return;
}
final dir = await getTemporaryDirectory();
final cacheDir = '${dir.path}/map_tiles_cache';
// Initialiser le HiveCacheStore
final cacheStore = HiveCacheStore(
cacheDir,
hiveBoxName: 'mapTilesCache',
);
// Initialiser le CachedTileProvider
_tileProvider = CachedTileProvider(
maxStale: const Duration(days: 30),
store: cacheStore,
);
debugPrint('MapboxMap: Cache initialisé dans $cacheDir');
if (mounted) {
setState(() {
_cacheInitialized = true;
});
}
debugPrint('MapboxMap: Cache initialisé avec succès pour ${widget.useOpenStreetMap ? "OpenStreetMap" : "Mapbox"}');
} catch (e) {
debugPrint('MapboxMap: Erreur lors de l\'initialisation du cache: $e');
// En cas d'erreur, on continue sans cache
if (mounted) {
setState(() {
_cacheInitialized = true;
_tileProvider = null; // Utiliser NetworkTileProvider en fallback
});
}
}
}
@override
void dispose() {
// Ne pas disposer le contrôleur s'il a été fourni de l'extérieur
if (widget.mapController == null) {
_mapController.dispose();
}
super.dispose();
}
/// Construit un bouton de contrôle de carte
Widget _buildMapButton({
required IconData icon,
required VoidCallback onPressed,
}) {
return Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 6,
offset: const Offset(0, 3),
),
],
),
child: IconButton(
icon: Icon(icon, size: 20),
onPressed: onPressed,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
);
}
@override
Widget build(BuildContext context) {
String urlTemplate;
if (widget.useOpenStreetMap) {
// Utiliser OpenStreetMap comme alternative
urlTemplate = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';
debugPrint('MapboxMap: Utilisation d\'OpenStreetMap');
} else {
// Déterminer l'URL du template de tuiles Mapbox
// Utiliser l'environnement actuel pour obtenir la bonne clé API
final String environment = ApiService.instance.getCurrentEnvironment();
final String mapboxToken = AppKeys.getMapboxApiKey(environment);
// Essayer différentes API Mapbox selon la plateforme
if (kIsWeb) {
// Sur web, on peut utiliser l'API styles
urlTemplate = 'https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/256/{z}/{x}/{y}@2x?access_token=$mapboxToken';
} else {
// Sur mobile, utiliser l'API v4 qui fonctionne mieux avec les tokens standards
// Format: mapbox.streets pour les rues, mapbox.satellite pour satellite
urlTemplate = 'https://api.tiles.mapbox.com/v4/mapbox.streets/{z}/{x}/{y}@2x.png?access_token=$mapboxToken';
}
}
// Afficher un indicateur pendant l'initialisation du cache
if (!_cacheInitialized) {
return Stack(
children: [
// Carte sans cache en attendant
_buildMapContent(urlTemplate),
// Indicateur discret
const Positioned(
top: 8,
right: 8,
child: Card(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
),
SizedBox(width: 8),
Text('Initialisation du cache...', style: TextStyle(fontSize: 12)),
],
),
),
),
),
],
);
}
return _buildMapContent(urlTemplate);
}
Widget _buildMapContent(String urlTemplate) {
return Stack(
children: [
// Carte principale
FlutterMap(
mapController: _mapController,
options: MapOptions(
initialCenter: widget.initialPosition,
initialZoom: widget.initialZoom,
minZoom: 7.0, // Zoom minimum pour éviter que les tuiles ne se chargent pas
maxZoom: 20.0, // Zoom maximum
interactionOptions: InteractionOptions(
enableMultiFingerGestureRace: true,
flags: widget.disableDrag
? InteractiveFlag.all & ~InteractiveFlag.drag
: InteractiveFlag.all,
),
onMapEvent: (event) {
if (event is MapEventMove) {
// Mise à jour du zoom sans rebuild (la variable n'est pas utilisée dans le UI)
_currentZoom = _mapController.camera.zoom;
}
// Appeler le callback externe si fourni
if (widget.onMapEvent != null) {
widget.onMapEvent!(event);
}
},
),
children: [
// Tuiles de la carte (Mapbox)
TileLayer(
urlTemplate: urlTemplate,
userAgentPackageName: 'app3.geosector.fr',
maxNativeZoom: 19,
maxZoom: 20,
minZoom: 7,
// Utiliser le cache sur mobile, NetworkTileProvider sur Web
tileProvider: !kIsWeb && _cacheInitialized && _tileProvider != null
? _tileProvider!
: NetworkTileProvider(
headers: {
'User-Agent': 'geosector_app/3.3.1',
'Accept': '*/*',
},
),
errorTileCallback: (tile, error, stackTrace) {
// Réduire les logs d'erreur pour ne pas polluer la console
if (!error.toString().contains('abortTrigger')) {
debugPrint('MapboxMap: Erreur de chargement de tuile: $error');
}
},
),
// Polygones
if (widget.polygons != null && widget.polygons!.isNotEmpty) PolygonLayer(polygons: widget.polygons!),
// Marqueurs de labels (sous les marqueurs principaux)
if (widget.labelMarkers != null && widget.labelMarkers!.isNotEmpty) MarkerLayer(markers: widget.labelMarkers!),
// Polylines
if (widget.polylines != null && widget.polylines!.isNotEmpty) PolylineLayer(polylines: widget.polylines!),
// Marqueurs principaux (au-dessus de tout)
if (widget.markers != null && widget.markers!.isNotEmpty) MarkerLayer(markers: widget.markers!),
],
),
// Boutons de contrôle
if (widget.showControls)
Positioned(
bottom: 16,
right: 16,
child: Column(
children: [
// Bouton de zoom +
_buildMapButton(
icon: Icons.add,
onPressed: () {
_mapController.move(
_mapController.camera.center,
_mapController.camera.zoom + 1,
);
},
),
const SizedBox(height: 8),
// Bouton de zoom -
_buildMapButton(
icon: Icons.remove,
onPressed: () {
_mapController.move(
_mapController.camera.center,
_mapController.camera.zoom - 1,
);
},
),
const SizedBox(height: 8),
// Bouton de localisation
_buildMapButton(
icon: Icons.my_location,
onPressed: () {
_mapController.move(
widget.initialPosition,
15,
);
},
),
],
),
),
],
);
}
}