feat: Gestion des secteurs et migration v3.0.4+304
- Ajout système complet de gestion des secteurs avec contours géographiques - Import des contours départementaux depuis GeoJSON - API REST pour la gestion des secteurs (/api/sectors) - Service de géolocalisation pour déterminer les secteurs - Migration base de données avec tables x_departements_contours et sectors_adresses - Interface Flutter pour visualisation et gestion des secteurs - Ajout thème sombre dans l'application - Corrections diverses et optimisations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
0
app/lib/core/services/api_service.dart
Normal file → Executable file
0
app/lib/core/services/api_service.dart
Normal file → Executable file
0
app/lib/core/services/app_info_service.dart
Normal file → Executable file
0
app/lib/core/services/app_info_service.dart
Normal file → Executable file
0
app/lib/core/services/connectivity_service.dart
Normal file → Executable file
0
app/lib/core/services/connectivity_service.dart
Normal file → Executable file
24
app/lib/core/services/current_amicale_service.dart
Normal file → Executable file
24
app/lib/core/services/current_amicale_service.dart
Normal file → Executable file
@@ -6,7 +6,8 @@ import 'package:geosector_app/core/services/current_user_service.dart';
|
||||
|
||||
class CurrentAmicaleService extends ChangeNotifier {
|
||||
static CurrentAmicaleService? _instance;
|
||||
static CurrentAmicaleService get instance => _instance ??= CurrentAmicaleService._internal();
|
||||
static CurrentAmicaleService get instance =>
|
||||
_instance ??= CurrentAmicaleService._internal();
|
||||
CurrentAmicaleService._internal();
|
||||
|
||||
AmicaleModel? _currentAmicale;
|
||||
@@ -23,7 +24,8 @@ class CurrentAmicaleService extends ChangeNotifier {
|
||||
? '${_currentAmicale!.adresse1} ${_currentAmicale!.adresse2}'.trim()
|
||||
: null;
|
||||
String? get amicaleFullAddress => _currentAmicale != null
|
||||
? '${amicaleAddress ?? ''} ${_currentAmicale!.codePostal} ${_currentAmicale!.ville}'.trim()
|
||||
? '${amicaleAddress ?? ''} ${_currentAmicale!.codePostal} ${_currentAmicale!.ville}'
|
||||
.trim()
|
||||
: null;
|
||||
bool get amicaleIsActive => _currentAmicale?.chkActive ?? false;
|
||||
bool get isClient => _currentAmicale?.fkType == 1;
|
||||
@@ -33,13 +35,11 @@ class CurrentAmicaleService extends ChangeNotifier {
|
||||
_currentAmicale?.gpsLat.isNotEmpty == true &&
|
||||
_currentAmicale?.gpsLng.isNotEmpty == true;
|
||||
|
||||
double? get latitude => hasGpsCoordinates
|
||||
? double.tryParse(_currentAmicale!.gpsLat)
|
||||
: null;
|
||||
double? get latitude =>
|
||||
hasGpsCoordinates ? double.tryParse(_currentAmicale!.gpsLat) : null;
|
||||
|
||||
double? get longitude => hasGpsCoordinates
|
||||
? double.tryParse(_currentAmicale!.gpsLng)
|
||||
: null;
|
||||
double? get longitude =>
|
||||
hasGpsCoordinates ? double.tryParse(_currentAmicale!.gpsLng) : null;
|
||||
|
||||
// === SETTERS ===
|
||||
Future<void> setAmicale(AmicaleModel? amicale) async {
|
||||
@@ -85,7 +85,7 @@ class CurrentAmicaleService extends ChangeNotifier {
|
||||
} else {
|
||||
// Si l'amicale n'est pas la bonne, la chercher ou l'effacer
|
||||
_currentAmicale = null;
|
||||
debugPrint('⚠️ Amicale ${amicaleId} non trouvée dans Hive');
|
||||
debugPrint('⚠️ Amicale $amicaleId non trouvée dans Hive');
|
||||
}
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
@@ -119,7 +119,7 @@ class CurrentAmicaleService extends ChangeNotifier {
|
||||
}
|
||||
|
||||
Future<void> loadFromHive() async {
|
||||
try {
|
||||
try {
|
||||
final box = Hive.box<AmicaleModel>(AppKeys.amicaleBoxName);
|
||||
_currentAmicale = box.get('current_amicale');
|
||||
|
||||
@@ -133,7 +133,7 @@ class CurrentAmicaleService extends ChangeNotifier {
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur chargement amicale depuis Hive: $e');
|
||||
_currentAmicale = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === RESET POUR TESTS ===
|
||||
@@ -141,4 +141,4 @@ class CurrentAmicaleService extends ChangeNotifier {
|
||||
_instance?._currentAmicale = null;
|
||||
_instance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0
app/lib/core/services/current_user_service.dart
Normal file → Executable file
0
app/lib/core/services/current_user_service.dart
Normal file → Executable file
159
app/lib/core/services/data_loading_service.dart
Normal file → Executable file
159
app/lib/core/services/data_loading_service.dart
Normal file → Executable file
@@ -3,7 +3,6 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||
import 'package:geosector_app/core/services/api_service.dart';
|
||||
import 'package:geosector_app/core/services/hive_web_fix.dart';
|
||||
import 'package:geosector_app/core/data/models/operation_model.dart';
|
||||
import 'package:geosector_app/core/data/models/sector_model.dart';
|
||||
@@ -12,7 +11,6 @@ import 'package:geosector_app/core/data/models/membre_model.dart';
|
||||
import 'package:geosector_app/core/data/models/user_sector_model.dart';
|
||||
import 'package:geosector_app/core/data/models/amicale_model.dart';
|
||||
import 'package:geosector_app/core/repositories/client_repository.dart';
|
||||
import 'package:geosector_app/core/repositories/amicale_repository.dart';
|
||||
import 'package:geosector_app/chat/models/conversation_model.dart';
|
||||
import 'package:geosector_app/chat/models/message_model.dart';
|
||||
import 'package:geosector_app/core/models/loading_state.dart';
|
||||
@@ -20,7 +18,8 @@ import 'package:geosector_app/core/models/loading_state.dart';
|
||||
/// Service singleton pour gérer le chargement et la gestion des données au login
|
||||
class DataLoadingService extends ChangeNotifier {
|
||||
static DataLoadingService? _instance;
|
||||
static DataLoadingService get instance => _instance ??= DataLoadingService._internal();
|
||||
static DataLoadingService get instance =>
|
||||
_instance ??= DataLoadingService._internal();
|
||||
DataLoadingService._internal();
|
||||
|
||||
// État du chargement
|
||||
@@ -43,14 +42,22 @@ class DataLoadingService extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// === GETTERS POUR LES BOXES ===
|
||||
Box<OperationModel> get _operationBox => Hive.box<OperationModel>(AppKeys.operationsBoxName);
|
||||
Box<SectorModel> get _sectorBox => Hive.box<SectorModel>(AppKeys.sectorsBoxName);
|
||||
Box<PassageModel> get _passageBox => Hive.box<PassageModel>(AppKeys.passagesBoxName);
|
||||
Box<MembreModel> get _membreBox => Hive.box<MembreModel>(AppKeys.membresBoxName);
|
||||
Box<UserSectorModel> get _userSectorBox => Hive.box<UserSectorModel>(AppKeys.userSectorBoxName);
|
||||
Box<AmicaleModel> get _amicaleBox => Hive.box<AmicaleModel>(AppKeys.amicaleBoxName);
|
||||
Box<ConversationModel> get _chatConversationBox => Hive.box<ConversationModel>(AppKeys.chatConversationsBoxName);
|
||||
Box<MessageModel> get _chatMessageBox => Hive.box<MessageModel>(AppKeys.chatMessagesBoxName);
|
||||
Box<OperationModel> get _operationBox =>
|
||||
Hive.box<OperationModel>(AppKeys.operationsBoxName);
|
||||
Box<SectorModel> get _sectorBox =>
|
||||
Hive.box<SectorModel>(AppKeys.sectorsBoxName);
|
||||
Box<PassageModel> get _passageBox =>
|
||||
Hive.box<PassageModel>(AppKeys.passagesBoxName);
|
||||
Box<MembreModel> get _membreBox =>
|
||||
Hive.box<MembreModel>(AppKeys.membresBoxName);
|
||||
Box<UserSectorModel> get _userSectorBox =>
|
||||
Hive.box<UserSectorModel>(AppKeys.userSectorBoxName);
|
||||
Box<AmicaleModel> get _amicaleBox =>
|
||||
Hive.box<AmicaleModel>(AppKeys.amicaleBoxName);
|
||||
Box<ConversationModel> get _chatConversationBox =>
|
||||
Hive.box<ConversationModel>(AppKeys.chatConversationsBoxName);
|
||||
Box<MessageModel> get _chatMessageBox =>
|
||||
Hive.box<MessageModel>(AppKeys.chatMessagesBoxName);
|
||||
Box get _settingsBox => Hive.box(AppKeys.settingsBoxName);
|
||||
|
||||
/// Traite toutes les données reçues de l'API lors du login
|
||||
@@ -71,8 +78,8 @@ class DataLoadingService extends ChangeNotifier {
|
||||
await _processOperations(apiResult['operations']);
|
||||
}
|
||||
|
||||
if (apiResult['secteurs'] != null) {
|
||||
await _processSectors(apiResult['secteurs']);
|
||||
if (apiResult['sectors'] != null) {
|
||||
await _processSectors(apiResult['sectors']);
|
||||
}
|
||||
|
||||
if (apiResult['passages'] != null) {
|
||||
@@ -86,8 +93,16 @@ class DataLoadingService extends ChangeNotifier {
|
||||
await _processMembres(apiResult['membres']);
|
||||
}
|
||||
|
||||
if (apiResult['userSecteurs'] != null) {
|
||||
await _processUserSectors(apiResult['userSecteurs']);
|
||||
// ATTENTION : L'API envoie 'users_sectors', pas 'userSecteurs'
|
||||
if (apiResult['users_sectors'] != null) {
|
||||
debugPrint('📋 Traitement des associations users_sectors depuis le login');
|
||||
await _processUserSectors(apiResult['users_sectors'], clearAll: true);
|
||||
} else if (apiResult['userSecteurs'] != null) {
|
||||
// Fallback pour compatibilité si l'API change
|
||||
debugPrint('📋 Traitement des associations userSecteurs depuis le login (fallback)');
|
||||
await _processUserSectors(apiResult['userSecteurs'], clearAll: true);
|
||||
} else {
|
||||
debugPrint('⚠️ Aucune donnée users_sectors/userSecteurs trouvée dans la réponse du login');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur lors du chargement: $e');
|
||||
@@ -107,7 +122,8 @@ class DataLoadingService extends ChangeNotifier {
|
||||
|
||||
for (final boxName in requiredBoxes) {
|
||||
if (!Hive.isBoxOpen(boxName)) {
|
||||
throw Exception('La boîte $boxName n\'est pas ouverte. Redémarrez l\'application.');
|
||||
throw Exception(
|
||||
'La boîte $boxName n\'est pas ouverte. Redémarrez l\'application.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,8 +155,10 @@ class DataLoadingService extends ChangeNotifier {
|
||||
}
|
||||
|
||||
/// Méthode publique pour traiter les associations user-sectors depuis l'extérieur
|
||||
Future<void> processUserSectorsFromApi(dynamic userSectorsData) async {
|
||||
await _processUserSectors(userSectorsData);
|
||||
/// [clearAll] : si true, vide toute la box avant d'ajouter (pour le login)
|
||||
/// si false, ne supprime que les secteurs concernés (pour les mises à jour)
|
||||
Future<void> processUserSectorsFromApi(dynamic userSectorsData, {bool clearAll = false}) async {
|
||||
await _processUserSectors(userSectorsData, clearAll: clearAll);
|
||||
}
|
||||
|
||||
/// Méthode publique pour traiter les opérations depuis l'extérieur
|
||||
@@ -225,27 +243,41 @@ class DataLoadingService extends ChangeNotifier {
|
||||
List<dynamic> sectorsList;
|
||||
if (sectorsData is List) {
|
||||
sectorsList = sectorsData;
|
||||
debugPrint('📋 ${sectorsList.length} secteurs à traiter (format: List directe)');
|
||||
} else if (sectorsData is Map && sectorsData.containsKey('data')) {
|
||||
sectorsList = sectorsData['data'] as List<dynamic>;
|
||||
debugPrint('📋 ${sectorsList.length} secteurs à traiter (format: Map avec data)');
|
||||
} else {
|
||||
debugPrint('⚠️ Format de données de secteurs non reconnu');
|
||||
debugPrint('⚠️ Format de données de secteurs non reconnu: ${sectorsData.runtimeType}');
|
||||
debugPrint('⚠️ Contenu: ${sectorsData.toString().substring(0, 200)}...');
|
||||
return;
|
||||
}
|
||||
|
||||
await _sectorBox.clear();
|
||||
|
||||
int count = 0;
|
||||
int errorCount = 0;
|
||||
for (final sectorData in sectorsList) {
|
||||
try {
|
||||
debugPrint('🔄 Traitement secteur ID: ${sectorData['id']}, libelle: "${sectorData['libelle']}"');
|
||||
final sector = SectorModel.fromJson(sectorData);
|
||||
await _sectorBox.put(sector.id, sector);
|
||||
count++;
|
||||
} catch (e) {
|
||||
debugPrint('⚠️ Erreur traitement secteur: $e');
|
||||
errorCount++;
|
||||
debugPrint('⚠️ Erreur traitement secteur ${sectorData['id']}: $e');
|
||||
debugPrint('⚠️ Données problématiques: $sectorData');
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('✅ $count secteurs stockés');
|
||||
debugPrint('✅ $count secteurs stockés${errorCount > 0 ? ' ($errorCount erreurs ignorées)' : ''}');
|
||||
|
||||
// Vérification finale
|
||||
final storedSectors = _sectorBox.values.toList();
|
||||
debugPrint('🔍 Vérification: ${storedSectors.length} secteurs dans la box');
|
||||
for (final sector in storedSectors) {
|
||||
debugPrint(' - Secteur ${sector.id}: "${sector.libelle}" (${sector.color})');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur traitement secteurs: $e');
|
||||
}
|
||||
@@ -286,7 +318,8 @@ class DataLoadingService extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('✅ $count passages stockés${errorCount > 0 ? ' ($errorCount erreurs ignorées)' : ''}');
|
||||
debugPrint(
|
||||
'✅ $count passages stockés${errorCount > 0 ? ' ($errorCount erreurs ignorées)' : ''}');
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur traitement passages: $e');
|
||||
}
|
||||
@@ -305,7 +338,8 @@ class DataLoadingService extends ChangeNotifier {
|
||||
await _amicaleBox.clear();
|
||||
try {
|
||||
// Les données d'amicale sont un objet unique
|
||||
final Map<String, dynamic> amicaleMap = Map<String, dynamic>.from(amicaleData as Map);
|
||||
final Map<String, dynamic> amicaleMap =
|
||||
Map<String, dynamic>.from(amicaleData as Map);
|
||||
final amicale = AmicaleModel.fromJson(amicaleMap);
|
||||
await _amicaleBox.put(amicale.id, amicale);
|
||||
debugPrint('✅ Amicale stockée: ${amicale.name} (ID: ${amicale.id})');
|
||||
@@ -353,15 +387,17 @@ class DataLoadingService extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('✅ $count membres stockés${errorCount > 0 ? ' ($errorCount erreurs ignorées)' : ''}');
|
||||
debugPrint(
|
||||
'✅ $count membres stockés${errorCount > 0 ? ' ($errorCount erreurs ignorées)' : ''}');
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur traitement membres: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _processUserSectors(dynamic userSectorsData) async {
|
||||
Future<void> _processUserSectors(dynamic userSectorsData, {bool clearAll = false}) async {
|
||||
try {
|
||||
debugPrint('🔗 Traitement des associations utilisateurs-secteurs...');
|
||||
debugPrint('Type de données reçues: ${userSectorsData.runtimeType}');
|
||||
|
||||
if (userSectorsData == null) {
|
||||
debugPrint('ℹ️ Aucune association utilisateur-secteur à traiter');
|
||||
@@ -371,27 +407,91 @@ class DataLoadingService extends ChangeNotifier {
|
||||
List<dynamic> userSectorsList;
|
||||
if (userSectorsData is List) {
|
||||
userSectorsList = userSectorsData;
|
||||
} else if (userSectorsData is Map && userSectorsData.containsKey('data')) {
|
||||
debugPrint('✅ Données au format List avec ${userSectorsList.length} éléments');
|
||||
} else if (userSectorsData is Map &&
|
||||
userSectorsData.containsKey('data')) {
|
||||
userSectorsList = userSectorsData['data'] as List<dynamic>;
|
||||
debugPrint('✅ Données au format Map[data] avec ${userSectorsList.length} éléments');
|
||||
} else {
|
||||
debugPrint('⚠️ Format de données d\'associations non reconnu');
|
||||
return;
|
||||
}
|
||||
|
||||
await _userSectorBox.clear();
|
||||
// Vérifier si la box est ouverte
|
||||
if (!Hive.isBoxOpen(AppKeys.userSectorBoxName)) {
|
||||
debugPrint('❌ La box UserSector n\'est pas ouverte!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (clearAll) {
|
||||
// Mode login : on vide toute la box
|
||||
await _userSectorBox.clear();
|
||||
debugPrint('📦 Box UserSector vidée complètement (mode login)');
|
||||
} else {
|
||||
// Mode mise à jour : on ne supprime que les associations des secteurs concernés
|
||||
// car l'API ne retourne que les associations pour le(s) secteur(s) modifié(s)
|
||||
|
||||
// Identifier les secteurs concernés dans les nouvelles données
|
||||
final Set<int> sectorsToUpdate = {};
|
||||
for (final data in userSectorsList) {
|
||||
if (data['fk_sector'] != null) {
|
||||
final fkSector = data['fk_sector'] is String
|
||||
? int.parse(data['fk_sector'])
|
||||
: data['fk_sector'] as int;
|
||||
sectorsToUpdate.add(fkSector);
|
||||
}
|
||||
}
|
||||
|
||||
// Supprimer uniquement les associations des secteurs concernés
|
||||
if (sectorsToUpdate.isNotEmpty) {
|
||||
debugPrint('🗑️ Suppression des associations pour les secteurs: $sectorsToUpdate');
|
||||
final keysToDelete = <dynamic>[];
|
||||
|
||||
for (var i = 0; i < _userSectorBox.length; i++) {
|
||||
final key = _userSectorBox.keyAt(i);
|
||||
final userSector = _userSectorBox.getAt(i);
|
||||
if (userSector != null && sectorsToUpdate.contains(userSector.fkSector)) {
|
||||
keysToDelete.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
for (final key in keysToDelete) {
|
||||
await _userSectorBox.delete(key);
|
||||
}
|
||||
|
||||
debugPrint('📦 ${keysToDelete.length} associations supprimées');
|
||||
}
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (final userSectorData in userSectorsList) {
|
||||
try {
|
||||
final userSector = UserSectorModel.fromJson(userSectorData);
|
||||
await _userSectorBox.put('${userSector.id}_${userSector.fkSector}', userSector);
|
||||
final key = '${userSector.id}_${userSector.fkSector}';
|
||||
await _userSectorBox.put(key, userSector);
|
||||
debugPrint('✅ Association sauvegardée: ${userSector.firstName} ${userSector.name} (ID: ${userSector.id}) -> Secteur ${userSector.fkSector}');
|
||||
count++;
|
||||
} catch (e) {
|
||||
debugPrint('⚠️ Erreur traitement association: $e');
|
||||
debugPrint('⚠️ Données problématiques: $userSectorData');
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('✅ $count associations stockées');
|
||||
|
||||
// Vérifier le contenu de la box après sauvegarde
|
||||
debugPrint('📦 Contenu de la box UserSector après sauvegarde:');
|
||||
debugPrint(' - Nombre d\'entrées: ${_userSectorBox.length}');
|
||||
for (var i = 0; i < _userSectorBox.length && i < 5; i++) {
|
||||
final key = _userSectorBox.keyAt(i);
|
||||
final value = _userSectorBox.getAt(i);
|
||||
if (value != null) {
|
||||
debugPrint(' - [$key]: ${value.firstName} ${value.name} (ID: ${value.id}) -> Secteur ${value.fkSector}');
|
||||
}
|
||||
}
|
||||
if (_userSectorBox.length > 5) {
|
||||
debugPrint(' ... et ${_userSectorBox.length - 5} autres associations');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur traitement associations: $e');
|
||||
}
|
||||
@@ -449,7 +549,8 @@ class DataLoadingService extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('✅ Nettoyage Android terminé. $filesDeleted fichiers supprimés');
|
||||
debugPrint(
|
||||
'✅ Nettoyage Android terminé. $filesDeleted fichiers supprimés');
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur nettoyage Android: $e');
|
||||
}
|
||||
|
||||
0
app/lib/core/services/hive_adapters.dart
Normal file → Executable file
0
app/lib/core/services/hive_adapters.dart
Normal file → Executable file
32
app/lib/core/services/hive_reset_service.dart
Normal file → Executable file
32
app/lib/core/services/hive_reset_service.dart
Normal file → Executable file
@@ -4,7 +4,6 @@ import 'package:hive_flutter/hive_flutter.dart';
|
||||
// Importations conditionnelles pour le web vs non-web
|
||||
import 'js_interface.dart';
|
||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||
import 'package:geosector_app/core/services/hive_web_fix.dart';
|
||||
import 'package:geosector_app/core/data/models/user_model.dart';
|
||||
import 'package:geosector_app/core/data/models/amicale_model.dart';
|
||||
import 'package:geosector_app/core/data/models/client_model.dart';
|
||||
@@ -22,7 +21,8 @@ class HiveResetService {
|
||||
/// Réinitialise complètement Hive et recrée les boîtes nécessaires
|
||||
static Future<bool> resetAndRecreateHiveBoxes() async {
|
||||
try {
|
||||
debugPrint('HiveResetService: Début de la réinitialisation complète de Hive');
|
||||
debugPrint(
|
||||
'HiveResetService: Début de la réinitialisation complète de Hive');
|
||||
|
||||
// Approche plus radicale pour le web : supprimer directement IndexedDB
|
||||
if (kIsWeb) {
|
||||
@@ -67,7 +67,8 @@ class HiveResetService {
|
||||
// Rouvrir les boîtes essentielles
|
||||
await _reopenEssentialBoxes();
|
||||
|
||||
debugPrint('HiveResetService: Réinitialisation complète terminée avec succès');
|
||||
debugPrint(
|
||||
'HiveResetService: Réinitialisation complète terminée avec succès');
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('HiveResetService: Erreur lors de la réinitialisation: $e');
|
||||
@@ -75,31 +76,6 @@ class HiveResetService {
|
||||
}
|
||||
}
|
||||
|
||||
/// Ferme toutes les boîtes Hive ouvertes
|
||||
static Future<void> _closeAllBoxes() async {
|
||||
final boxNames = [
|
||||
AppKeys.userBoxName,
|
||||
AppKeys.amicaleBoxName,
|
||||
AppKeys.clientsBoxName,
|
||||
AppKeys.operationsBoxName,
|
||||
AppKeys.sectorsBoxName,
|
||||
AppKeys.passagesBoxName,
|
||||
AppKeys.settingsBoxName,
|
||||
AppKeys.membresBoxName,
|
||||
AppKeys.userSectorBoxName,
|
||||
AppKeys.chatConversationsBoxName,
|
||||
AppKeys.chatMessagesBoxName,
|
||||
AppKeys.regionsBoxName,
|
||||
];
|
||||
|
||||
for (final boxName in boxNames) {
|
||||
if (Hive.isBoxOpen(boxName)) {
|
||||
debugPrint('HiveResetService: Fermeture de la boîte $boxName');
|
||||
await Hive.box(boxName).close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Enregistre tous les adaptateurs Hive
|
||||
static void _registerAdapters() {
|
||||
debugPrint('HiveResetService: Enregistrement des adaptateurs Hive');
|
||||
|
||||
0
app/lib/core/services/hive_reset_state_service.dart
Normal file → Executable file
0
app/lib/core/services/hive_reset_state_service.dart
Normal file → Executable file
1
app/lib/core/services/hive_service.dart
Normal file → Executable file
1
app/lib/core/services/hive_service.dart
Normal file → Executable file
@@ -426,6 +426,7 @@ class HiveService {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === MÉTHODES UTILITAIRES ===
|
||||
|
||||
/// Vider une Box individuelle
|
||||
|
||||
3
app/lib/core/services/hive_web_fix.dart
Normal file → Executable file
3
app/lib/core/services/hive_web_fix.dart
Normal file → Executable file
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:js' as js;
|
||||
import 'package:geosector_app/core/services/js_stub.dart'
|
||||
if (dart.library.js) 'dart:js' as js;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||
|
||||
0
app/lib/core/services/js_interface.dart
Normal file → Executable file
0
app/lib/core/services/js_interface.dart
Normal file → Executable file
5
app/lib/core/services/js_stub.dart
Normal file → Executable file
5
app/lib/core/services/js_stub.dart
Normal file → Executable file
@@ -5,6 +5,11 @@ class JsContext {
|
||||
// Ne fait rien sur les plateformes non-web
|
||||
return null;
|
||||
}
|
||||
|
||||
bool hasProperty(String property) {
|
||||
// Retourne false sur les plateformes non-web
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Contexte JavaScript stub
|
||||
|
||||
0
app/lib/core/services/location_service.dart
Normal file → Executable file
0
app/lib/core/services/location_service.dart
Normal file → Executable file
19
app/lib/core/services/passage_data_service.dart
Normal file → Executable file
19
app/lib/core/services/passage_data_service.dart
Normal file → Executable file
@@ -48,9 +48,15 @@ class PassageDataService {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Trouver la date du passage le plus récent
|
||||
passagesToUse.sort((a, b) => b.passedAt.compareTo(a.passedAt));
|
||||
final DateTime referenceDate = passagesToUse.first.passedAt;
|
||||
// Trouver la date du passage le plus récent (exclure ceux sans date)
|
||||
final passagesWithDate = passagesToUse.where((p) => p.passedAt != null).toList();
|
||||
if (passagesWithDate.isEmpty) {
|
||||
debugPrint('Aucun passage avec date trouvé');
|
||||
return [];
|
||||
}
|
||||
|
||||
passagesWithDate.sort((a, b) => b.passedAt!.compareTo(a.passedAt!));
|
||||
final DateTime referenceDate = passagesWithDate.first.passedAt!;
|
||||
debugPrint(
|
||||
'Date de référence pour le graphique: ${DateFormat('dd/MM/yyyy').format(referenceDate)}');
|
||||
|
||||
@@ -84,12 +90,13 @@ class PassageDataService {
|
||||
|
||||
// Parcourir les passages et les regrouper par date et type
|
||||
for (final passage in passagesToUse) {
|
||||
if (passage.passedAt
|
||||
if (passage.passedAt != null &&
|
||||
passage.passedAt!
|
||||
.isAfter(startDate.subtract(const Duration(days: 1))) &&
|
||||
passage.passedAt
|
||||
passage.passedAt!
|
||||
.isBefore(referenceDate.add(const Duration(days: 1)))) {
|
||||
final dateStr =
|
||||
'${passage.passedAt.year}-${passage.passedAt.month.toString().padLeft(2, '0')}-${passage.passedAt.day.toString().padLeft(2, '0')}';
|
||||
'${passage.passedAt!.year}-${passage.passedAt!.month.toString().padLeft(2, '0')}-${passage.passedAt!.day.toString().padLeft(2, '0')}';
|
||||
final typeId = passage.fkType;
|
||||
|
||||
// Vérifier que le type n'est pas exclu
|
||||
|
||||
0
app/lib/core/services/sync_service.dart
Normal file → Executable file
0
app/lib/core/services/sync_service.dart
Normal file → Executable file
161
app/lib/core/services/theme_service.dart
Executable file
161
app/lib/core/services/theme_service.dart
Executable file
@@ -0,0 +1,161 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
/// Service pour gérer les préférences de thème de l'application
|
||||
/// Supporte la détection automatique du mode sombre/clair du système
|
||||
class ThemeService extends ChangeNotifier {
|
||||
static ThemeService? _instance;
|
||||
static ThemeService get instance => _instance ??= ThemeService._();
|
||||
|
||||
ThemeService._() {
|
||||
_init();
|
||||
}
|
||||
|
||||
// Préférences stockées
|
||||
SharedPreferences? _prefs;
|
||||
|
||||
// Mode de thème actuel
|
||||
ThemeMode _themeMode = ThemeMode.system;
|
||||
|
||||
// Clé pour stocker les préférences
|
||||
static const String _themeModeKey = 'theme_mode';
|
||||
|
||||
/// Mode de thème actuel
|
||||
ThemeMode get themeMode => _themeMode;
|
||||
|
||||
/// Détecte si le système est en mode sombre
|
||||
bool get isSystemDark {
|
||||
final brightness = SchedulerBinding.instance.platformDispatcher.platformBrightness;
|
||||
return brightness == Brightness.dark;
|
||||
}
|
||||
|
||||
/// Détermine si l'app doit utiliser le mode sombre
|
||||
bool get isDarkMode {
|
||||
switch (_themeMode) {
|
||||
case ThemeMode.light:
|
||||
return false;
|
||||
case ThemeMode.dark:
|
||||
return true;
|
||||
case ThemeMode.system:
|
||||
return isSystemDark;
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialise le service
|
||||
Future<void> _init() async {
|
||||
try {
|
||||
_prefs = await SharedPreferences.getInstance();
|
||||
await _loadThemeMode();
|
||||
|
||||
// Observer les changements du système
|
||||
SchedulerBinding.instance.platformDispatcher.onPlatformBrightnessChanged = () {
|
||||
_onSystemBrightnessChanged();
|
||||
};
|
||||
|
||||
debugPrint('🎨 ThemeService initialisé - Mode: $_themeMode, Système sombre: $isSystemDark');
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur initialisation ThemeService: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Charge le mode de thème depuis les préférences
|
||||
Future<void> _loadThemeMode() async {
|
||||
try {
|
||||
final savedMode = _prefs?.getString(_themeModeKey);
|
||||
if (savedMode != null) {
|
||||
_themeMode = ThemeMode.values.firstWhere(
|
||||
(mode) => mode.name == savedMode,
|
||||
orElse: () => ThemeMode.system,
|
||||
);
|
||||
}
|
||||
debugPrint('🎨 Mode de thème chargé: $_themeMode');
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur chargement thème: $e');
|
||||
_themeMode = ThemeMode.system;
|
||||
}
|
||||
}
|
||||
|
||||
/// Sauvegarde le mode de thème
|
||||
Future<void> _saveThemeMode() async {
|
||||
try {
|
||||
await _prefs?.setString(_themeModeKey, _themeMode.name);
|
||||
debugPrint('💾 Mode de thème sauvegardé: $_themeMode');
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur sauvegarde thème: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Appelée quand la luminosité du système change
|
||||
void _onSystemBrightnessChanged() {
|
||||
if (_themeMode == ThemeMode.system) {
|
||||
debugPrint('🌗 Changement luminosité système détecté - Sombre: $isSystemDark');
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Change le mode de thème
|
||||
Future<void> setThemeMode(ThemeMode mode) async {
|
||||
if (_themeMode != mode) {
|
||||
_themeMode = mode;
|
||||
await _saveThemeMode();
|
||||
notifyListeners();
|
||||
debugPrint('🎨 Mode de thème changé: $mode');
|
||||
}
|
||||
}
|
||||
|
||||
/// Basculer entre clair et sombre
|
||||
Future<void> toggleTheme() async {
|
||||
switch (_themeMode) {
|
||||
case ThemeMode.light:
|
||||
await setThemeMode(ThemeMode.dark);
|
||||
break;
|
||||
case ThemeMode.dark:
|
||||
await setThemeMode(ThemeMode.light);
|
||||
break;
|
||||
case ThemeMode.system:
|
||||
// Si système, basculer vers l'opposé du mode actuel du système
|
||||
await setThemeMode(isSystemDark ? ThemeMode.light : ThemeMode.dark);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// Retourner au mode système
|
||||
Future<void> useSystemTheme() async {
|
||||
await setThemeMode(ThemeMode.system);
|
||||
}
|
||||
|
||||
/// Forcer le mode clair
|
||||
Future<void> useLightTheme() async {
|
||||
await setThemeMode(ThemeMode.light);
|
||||
}
|
||||
|
||||
/// Forcer le mode sombre
|
||||
Future<void> useDarkTheme() async {
|
||||
await setThemeMode(ThemeMode.dark);
|
||||
}
|
||||
|
||||
/// Obtenir une description textuelle du mode actuel
|
||||
String get themeModeDescription {
|
||||
switch (_themeMode) {
|
||||
case ThemeMode.light:
|
||||
return 'Clair';
|
||||
case ThemeMode.dark:
|
||||
return 'Sombre';
|
||||
case ThemeMode.system:
|
||||
return 'Automatique (${isSystemDark ? 'sombre' : 'clair'})';
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtenir l'icône appropriée pour le mode actuel
|
||||
IconData get themeModeIcon {
|
||||
switch (_themeMode) {
|
||||
case ThemeMode.light:
|
||||
return Icons.light_mode;
|
||||
case ThemeMode.dark:
|
||||
return Icons.dark_mode;
|
||||
case ThemeMode.system:
|
||||
return Icons.brightness_auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user