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:
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user