feat: Version 3.6.3 - Carte IGN, mode boussole, corrections Flutter analyze

Nouvelles fonctionnalités:
- #215 Mode boussole + carte IGN/satellite (Mode terrain)
- #53 Définition zoom maximal pour éviter sur-zoom
- #14 Correction bug F5 déconnexion
- #204 Design couleurs flashy
- #205 Écrans utilisateurs simplifiés

Corrections Flutter analyze:
- Suppression warnings room.g.dart, chat_service.dart, api_service.dart
- 0 error, 0 warning, 30 infos (suggestions de style)

Autres:
- Intégration tuiles IGN Plan et IGN Ortho (geopf.fr)
- flutter_compass pour Android/iOS
- Réorganisation assets store

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-19 17:46:03 +01:00
parent 232940b1eb
commit 5b6808db25
62 changed files with 1428 additions and 3130 deletions

View File

@@ -8,13 +8,14 @@ import 'package:latlong2/latlong.dart';
import 'package:geolocator/geolocator.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:flutter_compass/flutter_compass.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
import 'package:geosector_app/core/data/models/passage_model.dart';
import 'package:geosector_app/core/services/api_service.dart';
import 'package:geosector_app/core/services/current_amicale_service.dart';
import 'package:geosector_app/presentation/widgets/passage_form_dialog.dart';
import 'package:geosector_app/presentation/widgets/passages/passages_list_widget.dart';
import 'package:geosector_app/presentation/widgets/grouped_passages_dialog.dart';
import 'package:geosector_app/presentation/widgets/mapbox_map.dart' show TileSource;
import 'package:geosector_app/app.dart';
import 'package:geosector_app/core/utils/api_exception.dart';
@@ -59,10 +60,20 @@ class _UserFieldModePageState extends State<UserFieldModePage>
// Listener pour les changements de la box passages
Box<PassageModel>? _passagesBox;
// Source des tuiles de la carte (IGN Plan ou IGN Ortho)
TileSource _tileSource = TileSource.ignPlan;
Box? _settingsBox;
// Mode boussole (Android/iOS uniquement)
bool _compassModeEnabled = false;
StreamSubscription<CompassEvent>? _compassSubscription;
double _currentHeading = 0.0;
@override
void initState() {
super.initState();
_initializeAnimations();
_loadTileSourceSetting();
// Écouter les changements de la Hive box passages pour rafraîchir la carte
_passagesBox = Hive.box<PassageModel>(AppKeys.passagesBoxName);
@@ -85,6 +96,26 @@ class _UserFieldModePageState extends State<UserFieldModePage>
}
}
// Charger le paramètre de source des tuiles depuis Hive
Future<void> _loadTileSourceSetting() async {
if (!Hive.isBoxOpen(AppKeys.settingsBoxName)) {
_settingsBox = await Hive.openBox(AppKeys.settingsBoxName);
} else {
_settingsBox = Hive.box(AppKeys.settingsBoxName);
}
final savedTileSource = _settingsBox?.get('mapTileSource');
if (savedTileSource != null && mounted) {
setState(() {
_tileSource = TileSource.values.firstWhere(
(t) => t.name == savedTileSource,
orElse: () => TileSource.ignPlan,
);
});
debugPrint('FieldMode: Source tuiles chargée = $_tileSource');
}
}
void _initializeWebMode() async {
// Essayer d'obtenir la position réelle depuis le navigateur
try {
@@ -539,6 +570,7 @@ class _UserFieldModePageState extends State<UserFieldModePage>
void dispose() {
_positionStreamSubscription?.cancel();
_qualityUpdateTimer?.cancel();
_compassSubscription?.cancel();
_gpsBlinkController.dispose();
_networkBlinkController.dispose();
_searchController.dispose();
@@ -546,6 +578,35 @@ class _UserFieldModePageState extends State<UserFieldModePage>
super.dispose();
}
// Activer/désactiver le mode boussole (Android/iOS uniquement)
void _toggleCompassMode() {
if (kIsWeb) return; // Pas de boussole sur web
setState(() {
_compassModeEnabled = !_compassModeEnabled;
});
if (_compassModeEnabled) {
// Activer l'écoute de la boussole
_compassSubscription = FlutterCompass.events?.listen((CompassEvent event) {
if (event.heading != null && mounted) {
setState(() {
_currentHeading = event.heading!;
});
// Faire pivoter la carte selon la direction
_mapController.rotate(-_currentHeading);
}
});
debugPrint('FieldMode: Mode boussole activé');
} else {
// Désactiver l'écoute et remettre la carte vers le nord
_compassSubscription?.cancel();
_compassSubscription = null;
_mapController.rotate(0);
debugPrint('FieldMode: Mode boussole désactivé');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -823,10 +884,6 @@ class _UserFieldModePageState extends State<UserFieldModePage>
);
}
final apiService = ApiService.instance;
final mapboxApiKey =
AppKeys.getMapboxApiKey(apiService.getCurrentEnvironment());
return Stack(
children: [
FlutterMap(
@@ -837,21 +894,36 @@ class _UserFieldModePageState extends State<UserFieldModePage>
initialZoom: 17,
maxZoom: 19,
minZoom: 10,
interactionOptions: const InteractionOptions(
interactionOptions: InteractionOptions(
enableMultiFingerGestureRace: true,
flags: InteractiveFlag.all & ~InteractiveFlag.rotate,
// Permettre la rotation uniquement si le mode boussole est activé
flags: _compassModeEnabled
? InteractiveFlag.all
: InteractiveFlag.all & ~InteractiveFlag.rotate,
),
),
children: [
TileLayer(
// Utiliser l'API v4 de Mapbox sur mobile ou OpenStreetMap en fallback
urlTemplate: kIsWeb
? 'https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/{z}/{x}/{y}?access_token=$mapboxApiKey'
: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', // OpenStreetMap temporairement sur mobile
// Utiliser les tuiles IGN (Plan ou Ortho selon le choix utilisateur)
urlTemplate: _tileSource == TileSource.ignOrtho
? 'https://data.geopf.fr/wmts?'
'REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0'
'&TILEMATRIXSET=PM'
'&LAYER=ORTHOIMAGERY.ORTHOPHOTOS'
'&STYLE=normal'
'&FORMAT=image/jpeg'
'&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}'
: 'https://data.geopf.fr/wmts?'
'REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0'
'&TILEMATRIXSET=PM'
'&LAYER=GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2'
'&STYLE=normal'
'&FORMAT=image/png'
'&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}',
userAgentPackageName: 'app3.geosector.fr',
additionalOptions: const {
'attribution': '© OpenStreetMap contributors',
},
maxNativeZoom: 19,
maxZoom: 20,
minZoom: 7,
),
// Markers des passages
MarkerLayer(
@@ -900,6 +972,56 @@ class _UserFieldModePageState extends State<UserFieldModePage>
child: const Icon(Icons.my_location),
),
),
// Boutons haut droite (IGN + Boussole)
Positioned(
top: 16,
right: 16,
child: Column(
children: [
// Bouton switch IGN Plan / Ortho
FloatingActionButton.small(
heroTag: 'tileSource',
backgroundColor: Colors.white,
foregroundColor: Colors.green[700],
tooltip: _tileSource == TileSource.ignPlan
? 'Passer en vue satellite'
: 'Passer en vue plan',
onPressed: () {
setState(() {
_tileSource = _tileSource == TileSource.ignPlan
? TileSource.ignOrtho
: TileSource.ignPlan;
});
// Sauvegarder le choix
_settingsBox?.put('mapTileSource', _tileSource.name);
debugPrint('FieldMode: Source tuiles = $_tileSource');
},
// L'icône montre l'action (vers quoi on bascule), pas l'état actuel
child: Icon(
_tileSource == TileSource.ignPlan
? Icons.satellite_alt // En mode plan → afficher satellite pour basculer
: Icons.map_outlined, // En mode ortho → afficher plan pour basculer
),
),
// Bouton mode boussole (uniquement sur mobile)
if (!kIsWeb) ...[
const SizedBox(height: 8),
FloatingActionButton.small(
heroTag: 'compass',
backgroundColor: _compassModeEnabled ? Colors.green[700] : Colors.white,
foregroundColor: _compassModeEnabled ? Colors.white : Colors.green[700],
tooltip: _compassModeEnabled
? 'Désactiver le mode boussole'
: 'Activer le mode boussole',
onPressed: _toggleCompassMode,
child: Icon(
_compassModeEnabled ? Icons.explore : Icons.explore_outlined,
),
),
],
],
),
),
],
);
}