feat: Release version 3.1.4 - Mode terrain et génération PDF

 Nouvelles fonctionnalités:
- Ajout du mode terrain pour utilisation mobile hors connexion
- Génération automatique de reçus PDF avec template personnalisé
- Révision complète du système de cartes avec amélioration des performances

🔧 Améliorations techniques:
- Refactoring du module chat avec architecture simplifiée
- Optimisation du système de sécurité NIST SP 800-63B
- Amélioration de la gestion des secteurs géographiques
- Support UTF-8 étendu pour les noms d'utilisateurs

📱 Application mobile:
- Nouveau mode terrain dans user_field_mode_page
- Interface utilisateur adaptative pour conditions difficiles
- Synchronisation offline améliorée

🗺️ Cartographie:
- Optimisation des performances MapBox
- Meilleure gestion des tuiles hors ligne
- Amélioration de l'affichage des secteurs

📄 Documentation:
- Ajout guide Android (ANDROID-GUIDE.md)
- Documentation sécurité API (API-SECURITY.md)
- Guide module chat (CHAT_MODULE.md)

🐛 Corrections:
- Résolution des erreurs 400 lors de la création d'utilisateurs
- Correction de la validation des noms d'utilisateurs
- Fix des problèmes de synchronisation chat

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-08-19 19:38:03 +02:00
parent c1f23c4345
commit 5ab03751e1
1823 changed files with 272663 additions and 198438 deletions

View File

@@ -0,0 +1,82 @@
import 'package:flutter/material.dart';
import 'package:geosector_app/chat/services/chat_info_service.dart';
/// Fonction helper pour créer une NavigationDestination avec badge
NavigationDestination createBadgedNavigationDestination({
required Icon icon,
required Icon selectedIcon,
required String label,
bool showBadge = false,
}) {
if (!showBadge) {
return NavigationDestination(
icon: icon,
selectedIcon: selectedIcon,
label: label,
);
}
// Créer les icônes avec badge
final badgedIcon = BadgedIcon(
icon: icon.icon!,
showBadge: true,
);
final badgedSelectedIcon = BadgedIcon(
icon: selectedIcon.icon!,
showBadge: true,
);
return NavigationDestination(
icon: badgedIcon,
selectedIcon: badgedSelectedIcon,
label: label,
);
}
/// Widget pour afficher un badge sur une icône
class BadgedIcon extends StatelessWidget {
final IconData icon;
final bool showBadge;
final Color? color;
final double? size;
const BadgedIcon({
super.key,
required this.icon,
this.showBadge = false,
this.color,
this.size,
});
@override
Widget build(BuildContext context) {
final iconWidget = Icon(icon, color: color, size: size);
if (!showBadge) {
return iconWidget;
}
return AnimatedBuilder(
animation: ChatInfoService.instance,
builder: (context, _) {
final unreadCount = ChatInfoService.instance.unreadMessages;
final badgeLabel = ChatInfoService.instance.badgeLabel;
if (unreadCount == 0) {
return iconWidget;
}
return Badge(
label: Text(
badgeLabel,
style: const TextStyle(fontSize: 10),
),
backgroundColor: Colors.red,
textColor: Colors.white,
child: iconWidget,
);
},
);
}
}

View File

@@ -1,5 +1,6 @@
import 'dart:io';
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:http_cache_file_store/http_cache_file_store.dart';
@@ -46,6 +47,9 @@ class MapboxMap extends StatefulWidget {
/// 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,
@@ -60,6 +64,7 @@ class MapboxMap extends StatefulWidget {
this.showControls = true,
this.mapStyle,
this.disableDrag = false,
this.useOpenStreetMap = false,
});
@override
@@ -70,7 +75,8 @@ class _MapboxMapState extends State<MapboxMap> {
/// Contrôleur de carte interne
late final MapController _mapController;
/// Niveau de zoom actuel
/// Niveau de zoom actuel (utilisé pour l'affichage futur)
// ignore: unused_field
double _currentZoom = 13.0;
/// Provider de cache pour les tuiles
@@ -91,7 +97,9 @@ class _MapboxMapState extends State<MapboxMap> {
Future<void> _initializeCache() async {
try {
final dir = await getTemporaryDirectory();
final cacheStore = FileCacheStore('${dir.path}${Platform.pathSeparator}MapboxTileCache');
// Utiliser un nom de cache différent selon le provider
final cacheName = widget.useOpenStreetMap ? 'OSMTileCache' : 'MapboxTileCache';
final cacheStore = FileCacheStore('${dir.path}${Platform.pathSeparator}$cacheName');
_tileProvider = CachedTileProvider(
store: cacheStore,
@@ -105,12 +113,14 @@ class _MapboxMapState extends State<MapboxMap> {
_cacheInitialized = true;
});
}
debugPrint('MapboxMap: Cache initialisé avec succès pour ${widget.useOpenStreetMap ? "OpenStreetMap" : "Mapbox"}');
} catch (e) {
debugPrint('Erreur lors de l\'initialisation du cache: $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
});
}
}
@@ -138,7 +148,7 @@ class _MapboxMapState extends State<MapboxMap> {
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
color: Colors.black.withValues(alpha: 0.2),
blurRadius: 6,
offset: const Offset(0, 3),
),
@@ -155,19 +165,41 @@ class _MapboxMapState extends State<MapboxMap> {
@override
Widget build(BuildContext context) {
// 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);
final String mapStyle = widget.mapStyle ?? 'mapbox/streets-v11';
final String urlTemplate = 'https://api.mapbox.com/styles/v1/$mapStyle/tiles/256/{z}/{x}/{y}@2x?access_token=$mapboxToken';
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';
}
// Debug pour vérifier la configuration
debugPrint('MapboxMap: Plateforme: ${kIsWeb ? "Web" : "Mobile"}');
debugPrint('MapboxMap: Environnement: $environment');
debugPrint('MapboxMap: Token: ${mapboxToken.substring(0, 10)}...'); // Afficher seulement le début du token
debugPrint('MapboxMap: URL Template: ${urlTemplate.substring(0, 50)}...');
}
// Afficher un indicateur pendant l'initialisation du cache
if (!_cacheInitialized) {
return Stack(
children: [
// Carte sans cache en attendant
_buildMapContent(urlTemplate, mapboxToken),
_buildMapContent(urlTemplate),
// Indicateur discret
const Positioned(
top: 8,
@@ -194,10 +226,10 @@ class _MapboxMapState extends State<MapboxMap> {
);
}
return _buildMapContent(urlTemplate, mapboxToken);
return _buildMapContent(urlTemplate);
}
Widget _buildMapContent(String urlTemplate, String mapboxToken) {
Widget _buildMapContent(String urlTemplate) {
return Stack(
children: [
// Carte principale
@@ -232,13 +264,24 @@ class _MapboxMapState extends State<MapboxMap> {
urlTemplate: urlTemplate,
userAgentPackageName: 'app.geosector.fr',
maxNativeZoom: 19,
additionalOptions: {
'accessToken': mapboxToken,
},
// Utilise le cache si disponible
maxZoom: 20,
minZoom: 1,
// Retirer tileSize pour utiliser la valeur par défaut
// Les additionalOptions ne sont pas nécessaires car le token est dans l'URL
// Utilise le cache si disponible sur web, NetworkTileProvider sur mobile
tileProvider: _cacheInitialized && _tileProvider != null
? _tileProvider!
: NetworkTileProvider(),
: NetworkTileProvider(
headers: {
'User-Agent': 'geosector_app/3.1.3',
'Accept': '*/*',
},
),
errorTileCallback: (tile, error, stackTrace) {
debugPrint('MapboxMap: Erreur de chargement de tuile: $error');
debugPrint('MapboxMap: Coordonnées de la tuile: ${tile.coordinates}');
debugPrint('MapboxMap: Stack trace: $stackTrace');
},
),
// Polygones