Initialisation du projet geosector complet (web + flutter)
This commit is contained in:
136
flutt/lib/core/constants/app_keys.dart
Normal file
136
flutt/lib/core/constants/app_keys.dart
Normal file
@@ -0,0 +1,136 @@
|
||||
/// Fichier contenant toutes les constantes utilisées dans l'application
|
||||
/// Centralise les clés, noms de boîtes Hive, et autres constantes
|
||||
/// pour faciliter la maintenance et éviter les erreurs de frappe
|
||||
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppKeys {
|
||||
// Noms des boîtes Hive
|
||||
static const String usersBoxName = 'users';
|
||||
static const String operationsBoxName = 'operations';
|
||||
static const String sectorsBoxName = 'sectors';
|
||||
static const String passagesBoxName = 'passages';
|
||||
static const String settingsBoxName = 'settings';
|
||||
static const String membresBoxName = 'membres';
|
||||
static const String chatConversationsBoxName = 'chat_conversations';
|
||||
static const String chatMessagesBoxName = 'chat_messages';
|
||||
|
||||
// Rôles utilisateurs
|
||||
static const int roleUser = 1;
|
||||
static const int roleAdmin1 = 2;
|
||||
static const int roleAdmin2 = 4;
|
||||
static const int roleAdmin3 = 9;
|
||||
|
||||
// URLs API
|
||||
static const String baseApiUrl = 'https://app.geosector.fr/api/geo';
|
||||
|
||||
// Endpoints API
|
||||
static const String loginEndpoint = '/login';
|
||||
static const String logoutEndpoint = '/logout';
|
||||
static const String registerEndpoint = '/register';
|
||||
static const String syncDataEndpoint = '/data/sync';
|
||||
static const String sectorsEndpoint = '/sectors';
|
||||
|
||||
// Durées
|
||||
static const Duration connectionTimeout = Duration(seconds: 5);
|
||||
static const Duration receiveTimeout = Duration(seconds: 30);
|
||||
static const Duration sessionDefaultExpiry = Duration(days: 7);
|
||||
|
||||
// Clés API externes
|
||||
static const String mapboxApiKey =
|
||||
'pk.eyJ1IjoicHZkNnNvZnQiLCJhIjoiY204dTNhNmd0MGV1ZzJqc2pnNnB0NjYxdSJ9.TA5Mvliyn91Oi01F_2Yuxw'; // À remplacer par votre clé API Mapbox
|
||||
|
||||
// Headers
|
||||
static const String sessionHeader = 'Authorization';
|
||||
|
||||
// En-têtes par défaut pour les requêtes API
|
||||
static const Map<String, String> defaultHeaders = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-App-Identifier': 'app.geosector.fr',
|
||||
'X-Client-Type': kIsWeb ? 'web' : 'mobile',
|
||||
'Accept': 'application/json',
|
||||
};
|
||||
|
||||
// Civilités
|
||||
static const Map<int, String> civilites = {
|
||||
1: 'M.',
|
||||
2: 'Mme',
|
||||
};
|
||||
|
||||
// Types de règlements
|
||||
static const Map<int, Map<String, dynamic>> typesReglements = {
|
||||
0: {
|
||||
'titre': 'Pas de règlement',
|
||||
'couleur': 0xFF757575, // Gris foncé
|
||||
'icon_data': Icons.money_off,
|
||||
},
|
||||
1: {
|
||||
'titre': 'Espèce',
|
||||
'couleur': 0xFFFFC107, // Jaune foncé (ambre)
|
||||
'icon_data': Icons.toll,
|
||||
},
|
||||
2: {
|
||||
'titre': 'Chèque',
|
||||
'couleur': 0xFF8BC34A, // Vert citron
|
||||
'icon_data': Icons.wallet,
|
||||
},
|
||||
3: {
|
||||
'titre': 'CB',
|
||||
'couleur': 0xFF00B0FF, // Bleu flashy (bleu clair accent),
|
||||
'icon_data': Icons.credit_card,
|
||||
},
|
||||
};
|
||||
|
||||
// Types de passages
|
||||
static const Map<int, Map<String, dynamic>> typesPassages = {
|
||||
1: {
|
||||
'titres': 'Effectués',
|
||||
'titre': 'Effectué',
|
||||
'couleur1': 0xFF4CAF50, // Vert success
|
||||
'couleur2': 0xFF4CAF50, // Vert success
|
||||
'couleur3': 0xFF4CAF50, // Vert success
|
||||
'icon_data': Icons.task_alt,
|
||||
},
|
||||
2: {
|
||||
'titres': 'À finaliser',
|
||||
'titre': 'À finaliser',
|
||||
'couleur1': 0xFFFFFFFF, // Blanc
|
||||
'couleur2': 0xFFFF9800, // Orange
|
||||
'couleur3': 0xFFE65100, // Orange foncé
|
||||
'icon_data': Icons.refresh,
|
||||
},
|
||||
3: {
|
||||
'titres': 'Refusés',
|
||||
'titre': 'Refusé',
|
||||
'couleur1': 0xFFF44336, // Rouge
|
||||
'couleur2': 0xFFF44336, // Rouge
|
||||
'couleur3': 0xFFF44336, // Rouge
|
||||
'icon_data': Icons.block,
|
||||
},
|
||||
4: {
|
||||
'titres': 'Dons',
|
||||
'titre': 'Don',
|
||||
'couleur1': 0xFF03A9F4, // Bleu ciel
|
||||
'couleur2': 0xFF03A9F4, // Bleu ciel
|
||||
'couleur3': 0xFF03A9F4, // Bleu ciel
|
||||
'icon_data': Icons.volunteer_activism,
|
||||
},
|
||||
5: {
|
||||
'titres': 'Lots',
|
||||
'titre': 'Lot',
|
||||
'couleur1': 0xFF0D47A1, // Bleu foncé
|
||||
'couleur2': 0xFF0D47A1, // Bleu foncé
|
||||
'couleur3': 0xFF0D47A1, // Bleu foncé
|
||||
'icon_data': Icons.layers,
|
||||
},
|
||||
6: {
|
||||
'titres': 'Maisons vides',
|
||||
'titre': 'Maison vide',
|
||||
'couleur1': 0xFF9E9E9E, // Gris
|
||||
'couleur2': 0xFF9E9E9E, // Gris
|
||||
'couleur3': 0xFF9E9E9E, // Gris
|
||||
'icon_data': Icons.home_outlined,
|
||||
},
|
||||
};
|
||||
}
|
||||
137
flutt/lib/core/data/models/membre_model.dart
Normal file
137
flutt/lib/core/data/models/membre_model.dart
Normal file
@@ -0,0 +1,137 @@
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'membre_model.g.dart';
|
||||
|
||||
@HiveType(typeId: 5) // Utilisation d'un typeId unique
|
||||
class MembreModel extends HiveObject {
|
||||
@HiveField(0)
|
||||
final int id;
|
||||
|
||||
@HiveField(1)
|
||||
final int fkRole;
|
||||
|
||||
@HiveField(2)
|
||||
final int fkTitre;
|
||||
|
||||
@HiveField(3)
|
||||
final String firstName;
|
||||
|
||||
@HiveField(4)
|
||||
final String? sectName;
|
||||
|
||||
@HiveField(5)
|
||||
final DateTime? dateNaissance;
|
||||
|
||||
@HiveField(6)
|
||||
final DateTime? dateEmbauche;
|
||||
|
||||
@HiveField(7)
|
||||
final int chkActive;
|
||||
|
||||
@HiveField(8)
|
||||
final String name;
|
||||
|
||||
@HiveField(9)
|
||||
final String username;
|
||||
|
||||
@HiveField(10)
|
||||
final String email;
|
||||
|
||||
MembreModel({
|
||||
required this.id,
|
||||
required this.fkRole,
|
||||
required this.fkTitre,
|
||||
required this.firstName,
|
||||
this.sectName,
|
||||
this.dateNaissance,
|
||||
this.dateEmbauche,
|
||||
required this.chkActive,
|
||||
required this.name,
|
||||
required this.username,
|
||||
required this.email,
|
||||
});
|
||||
|
||||
// Factory pour convertir depuis JSON (API)
|
||||
factory MembreModel.fromJson(Map<String, dynamic> json) {
|
||||
// Convertir l'ID en int, qu'il soit déjà int ou string
|
||||
final dynamic rawId = json['id'];
|
||||
final int id = rawId is String ? int.parse(rawId) : rawId as int;
|
||||
|
||||
// Convertir le rôle en int, qu'il soit déjà int ou string
|
||||
final dynamic rawRole = json['fk_role'];
|
||||
final int fkRole = rawRole is String ? int.parse(rawRole) : rawRole as int;
|
||||
|
||||
// Convertir le titre en int, qu'il soit déjà int ou string
|
||||
final dynamic rawTitre = json['fk_titre'];
|
||||
final int fkTitre =
|
||||
rawTitre is String ? int.parse(rawTitre) : rawTitre as int;
|
||||
|
||||
// Convertir le chkActive en int, qu'il soit déjà int ou string
|
||||
final dynamic rawActive = json['chk_active'];
|
||||
final int chkActive =
|
||||
rawActive is String ? int.parse(rawActive) : rawActive as int;
|
||||
|
||||
return MembreModel(
|
||||
id: id,
|
||||
fkRole: fkRole,
|
||||
fkTitre: fkTitre,
|
||||
firstName: json['first_name'] ?? '',
|
||||
sectName: json['sect_name'],
|
||||
dateNaissance: json['date_naissance'] != null
|
||||
? DateTime.parse(json['date_naissance'])
|
||||
: null,
|
||||
dateEmbauche: json['date_embauche'] != null
|
||||
? DateTime.parse(json['date_embauche'])
|
||||
: null,
|
||||
chkActive: chkActive,
|
||||
name: json['name'] ?? '',
|
||||
username: json['username'] ?? '',
|
||||
email: json['email'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
// Convertir en JSON pour l'API
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'fk_role': fkRole,
|
||||
'fk_titre': fkTitre,
|
||||
'first_name': firstName,
|
||||
'sect_name': sectName,
|
||||
'date_naissance': dateNaissance?.toIso8601String(),
|
||||
'date_embauche': dateEmbauche?.toIso8601String(),
|
||||
'chk_active': chkActive,
|
||||
'name': name,
|
||||
'username': username,
|
||||
'email': email,
|
||||
};
|
||||
}
|
||||
|
||||
// Copier avec de nouvelles valeurs
|
||||
MembreModel copyWith({
|
||||
int? fkRole,
|
||||
int? fkTitre,
|
||||
String? firstName,
|
||||
String? sectName,
|
||||
DateTime? dateNaissance,
|
||||
DateTime? dateEmbauche,
|
||||
int? chkActive,
|
||||
String? name,
|
||||
String? username,
|
||||
String? email,
|
||||
}) {
|
||||
return MembreModel(
|
||||
id: this.id,
|
||||
fkRole: fkRole ?? this.fkRole,
|
||||
fkTitre: fkTitre ?? this.fkTitre,
|
||||
firstName: firstName ?? this.firstName,
|
||||
sectName: sectName ?? this.sectName,
|
||||
dateNaissance: dateNaissance ?? this.dateNaissance,
|
||||
dateEmbauche: dateEmbauche ?? this.dateEmbauche,
|
||||
chkActive: chkActive ?? this.chkActive,
|
||||
name: name ?? this.name,
|
||||
username: username ?? this.username,
|
||||
email: email ?? this.email,
|
||||
);
|
||||
}
|
||||
}
|
||||
71
flutt/lib/core/data/models/membre_model.g.dart
Normal file
71
flutt/lib/core/data/models/membre_model.g.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'membre_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class MembreModelAdapter extends TypeAdapter<MembreModel> {
|
||||
@override
|
||||
final int typeId = 5;
|
||||
|
||||
@override
|
||||
MembreModel read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return MembreModel(
|
||||
id: fields[0] as int,
|
||||
fkRole: fields[1] as int,
|
||||
fkTitre: fields[2] as int,
|
||||
firstName: fields[3] as String,
|
||||
sectName: fields[4] as String?,
|
||||
dateNaissance: fields[5] as DateTime?,
|
||||
dateEmbauche: fields[6] as DateTime?,
|
||||
chkActive: fields[7] as int,
|
||||
name: fields[8] as String,
|
||||
username: fields[9] as String,
|
||||
email: fields[10] as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, MembreModel obj) {
|
||||
writer
|
||||
..writeByte(11)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.fkRole)
|
||||
..writeByte(2)
|
||||
..write(obj.fkTitre)
|
||||
..writeByte(3)
|
||||
..write(obj.firstName)
|
||||
..writeByte(4)
|
||||
..write(obj.sectName)
|
||||
..writeByte(5)
|
||||
..write(obj.dateNaissance)
|
||||
..writeByte(6)
|
||||
..write(obj.dateEmbauche)
|
||||
..writeByte(7)
|
||||
..write(obj.chkActive)
|
||||
..writeByte(8)
|
||||
..write(obj.name)
|
||||
..writeByte(9)
|
||||
..write(obj.username)
|
||||
..writeByte(10)
|
||||
..write(obj.email);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is MembreModelAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
85
flutt/lib/core/data/models/operation_model.dart
Normal file
85
flutt/lib/core/data/models/operation_model.dart
Normal file
@@ -0,0 +1,85 @@
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'operation_model.g.dart';
|
||||
|
||||
@HiveType(typeId: 1)
|
||||
class OperationModel extends HiveObject {
|
||||
@HiveField(0)
|
||||
final int id;
|
||||
|
||||
@HiveField(1)
|
||||
final String name;
|
||||
|
||||
@HiveField(2)
|
||||
final DateTime dateDebut;
|
||||
|
||||
@HiveField(3)
|
||||
final DateTime dateFin;
|
||||
|
||||
@HiveField(4)
|
||||
DateTime lastSyncedAt;
|
||||
|
||||
@HiveField(5)
|
||||
bool isActive;
|
||||
|
||||
@HiveField(6)
|
||||
bool isSynced;
|
||||
|
||||
OperationModel({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.dateDebut,
|
||||
required this.dateFin,
|
||||
required this.lastSyncedAt,
|
||||
this.isActive = true,
|
||||
this.isSynced = false,
|
||||
});
|
||||
|
||||
// Factory pour convertir depuis JSON (API)
|
||||
factory OperationModel.fromJson(Map<String, dynamic> json) {
|
||||
// Convertir l'ID en int, qu'il soit déjà int ou string
|
||||
final dynamic rawId = json['id'];
|
||||
final int id = rawId is String ? int.parse(rawId) : rawId as int;
|
||||
|
||||
return OperationModel(
|
||||
id: id,
|
||||
name: json['name'],
|
||||
dateDebut: DateTime.parse(json['date_deb']),
|
||||
dateFin: DateTime.parse(json['date_fin']),
|
||||
lastSyncedAt: DateTime.now(),
|
||||
isActive: true,
|
||||
isSynced: true,
|
||||
);
|
||||
}
|
||||
|
||||
// Convertir en JSON pour l'API
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'date_deb': dateDebut.toIso8601String().split('T')[0], // Format YYYY-MM-DD
|
||||
'date_fin': dateFin.toIso8601String().split('T')[0], // Format YYYY-MM-DD
|
||||
'is_active': isActive,
|
||||
};
|
||||
}
|
||||
|
||||
// Copier avec de nouvelles valeurs
|
||||
OperationModel copyWith({
|
||||
String? name,
|
||||
DateTime? dateDebut,
|
||||
DateTime? dateFin,
|
||||
bool? isActive,
|
||||
bool? isSynced,
|
||||
DateTime? lastSyncedAt,
|
||||
}) {
|
||||
return OperationModel(
|
||||
id: this.id,
|
||||
name: name ?? this.name,
|
||||
dateDebut: dateDebut ?? this.dateDebut,
|
||||
dateFin: dateFin ?? this.dateFin,
|
||||
lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt,
|
||||
isActive: isActive ?? this.isActive,
|
||||
isSynced: isSynced ?? this.isSynced,
|
||||
);
|
||||
}
|
||||
}
|
||||
59
flutt/lib/core/data/models/operation_model.g.dart
Normal file
59
flutt/lib/core/data/models/operation_model.g.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'operation_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class OperationModelAdapter extends TypeAdapter<OperationModel> {
|
||||
@override
|
||||
final int typeId = 1;
|
||||
|
||||
@override
|
||||
OperationModel read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return OperationModel(
|
||||
id: fields[0] as int,
|
||||
name: fields[1] as String,
|
||||
dateDebut: fields[2] as DateTime,
|
||||
dateFin: fields[3] as DateTime,
|
||||
lastSyncedAt: fields[4] as DateTime,
|
||||
isActive: fields[5] as bool,
|
||||
isSynced: fields[6] as bool,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, OperationModel obj) {
|
||||
writer
|
||||
..writeByte(7)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.name)
|
||||
..writeByte(2)
|
||||
..write(obj.dateDebut)
|
||||
..writeByte(3)
|
||||
..write(obj.dateFin)
|
||||
..writeByte(4)
|
||||
..write(obj.lastSyncedAt)
|
||||
..writeByte(5)
|
||||
..write(obj.isActive)
|
||||
..writeByte(6)
|
||||
..write(obj.isSynced);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is OperationModelAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
291
flutt/lib/core/data/models/passage_model.dart
Normal file
291
flutt/lib/core/data/models/passage_model.dart
Normal file
@@ -0,0 +1,291 @@
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'passage_model.g.dart';
|
||||
|
||||
@HiveType(typeId: 4)
|
||||
class PassageModel extends HiveObject {
|
||||
@HiveField(0)
|
||||
final int id;
|
||||
|
||||
@HiveField(1)
|
||||
final int fkOperation;
|
||||
|
||||
@HiveField(2)
|
||||
final int fkSector;
|
||||
|
||||
@HiveField(3)
|
||||
final int fkUser;
|
||||
|
||||
@HiveField(4)
|
||||
final int fkType;
|
||||
|
||||
@HiveField(5)
|
||||
final String fkAdresse;
|
||||
|
||||
@HiveField(6)
|
||||
final DateTime passedAt;
|
||||
|
||||
@HiveField(7)
|
||||
final String numero;
|
||||
|
||||
@HiveField(8)
|
||||
final String rue;
|
||||
|
||||
@HiveField(9)
|
||||
final String rueBis;
|
||||
|
||||
@HiveField(10)
|
||||
final String ville;
|
||||
|
||||
@HiveField(11)
|
||||
final String residence;
|
||||
|
||||
@HiveField(12)
|
||||
final int fkHabitat;
|
||||
|
||||
@HiveField(13)
|
||||
final String appt;
|
||||
|
||||
@HiveField(14)
|
||||
final String niveau;
|
||||
|
||||
@HiveField(15)
|
||||
final String gpsLat;
|
||||
|
||||
@HiveField(16)
|
||||
final String gpsLng;
|
||||
|
||||
@HiveField(17)
|
||||
final String nomRecu;
|
||||
|
||||
@HiveField(18)
|
||||
final String remarque;
|
||||
|
||||
@HiveField(19)
|
||||
final String montant;
|
||||
|
||||
@HiveField(20)
|
||||
final int fkTypeReglement;
|
||||
|
||||
@HiveField(21)
|
||||
final String emailErreur;
|
||||
|
||||
@HiveField(22)
|
||||
final int nbPassages;
|
||||
|
||||
@HiveField(23)
|
||||
final String name;
|
||||
|
||||
@HiveField(24)
|
||||
final String email;
|
||||
|
||||
@HiveField(25)
|
||||
final String phone;
|
||||
|
||||
@HiveField(26)
|
||||
DateTime lastSyncedAt;
|
||||
|
||||
@HiveField(27)
|
||||
bool isActive;
|
||||
|
||||
@HiveField(28)
|
||||
bool isSynced;
|
||||
|
||||
PassageModel({
|
||||
required this.id,
|
||||
required this.fkOperation,
|
||||
required this.fkSector,
|
||||
required this.fkUser,
|
||||
required this.fkType,
|
||||
required this.fkAdresse,
|
||||
required this.passedAt,
|
||||
required this.numero,
|
||||
required this.rue,
|
||||
this.rueBis = '',
|
||||
required this.ville,
|
||||
this.residence = '',
|
||||
required this.fkHabitat,
|
||||
this.appt = '',
|
||||
this.niveau = '',
|
||||
required this.gpsLat,
|
||||
required this.gpsLng,
|
||||
this.nomRecu = '',
|
||||
this.remarque = '',
|
||||
required this.montant,
|
||||
required this.fkTypeReglement,
|
||||
this.emailErreur = '',
|
||||
required this.nbPassages,
|
||||
required this.name,
|
||||
this.email = '',
|
||||
this.phone = '',
|
||||
required this.lastSyncedAt,
|
||||
this.isActive = true,
|
||||
this.isSynced = false,
|
||||
});
|
||||
|
||||
// Factory pour convertir depuis JSON (API)
|
||||
factory PassageModel.fromJson(Map<String, dynamic> json) {
|
||||
// Convertir l'ID en int, qu'il soit déjà int ou string
|
||||
final dynamic rawId = json['id'];
|
||||
final int id = rawId is String ? int.parse(rawId) : rawId as int;
|
||||
|
||||
// Convertir les autres champs numériques
|
||||
final dynamic rawFkOperation = json['fk_operation'];
|
||||
final int fkOperation = rawFkOperation is String ? int.parse(rawFkOperation) : rawFkOperation as int;
|
||||
|
||||
final dynamic rawFkSector = json['fk_sector'];
|
||||
final int fkSector = rawFkSector is String ? int.parse(rawFkSector) : rawFkSector as int;
|
||||
|
||||
final dynamic rawFkUser = json['fk_user'];
|
||||
final int fkUser = rawFkUser is String ? int.parse(rawFkUser) : rawFkUser as int;
|
||||
|
||||
final dynamic rawFkType = json['fk_type'];
|
||||
final int fkType = rawFkType is String ? int.parse(rawFkType) : rawFkType as int;
|
||||
|
||||
final dynamic rawFkHabitat = json['fk_habitat'];
|
||||
final int fkHabitat = rawFkHabitat is String ? int.parse(rawFkHabitat) : rawFkHabitat as int;
|
||||
|
||||
final dynamic rawFkTypeReglement = json['fk_type_reglement'];
|
||||
final int fkTypeReglement = rawFkTypeReglement is String ? int.parse(rawFkTypeReglement) : rawFkTypeReglement as int;
|
||||
|
||||
final dynamic rawNbPassages = json['nb_passages'];
|
||||
final int nbPassages = rawNbPassages is String ? int.parse(rawNbPassages) : rawNbPassages as int;
|
||||
|
||||
// Convertir la date
|
||||
final DateTime passedAt = DateTime.parse(json['passed_at']);
|
||||
|
||||
return PassageModel(
|
||||
id: id,
|
||||
fkOperation: fkOperation,
|
||||
fkSector: fkSector,
|
||||
fkUser: fkUser,
|
||||
fkType: fkType,
|
||||
fkAdresse: json['fk_adresse'] as String,
|
||||
passedAt: passedAt,
|
||||
numero: json['numero'] as String,
|
||||
rue: json['rue'] as String,
|
||||
rueBis: json['rue_bis'] as String? ?? '',
|
||||
ville: json['ville'] as String,
|
||||
residence: json['residence'] as String? ?? '',
|
||||
fkHabitat: fkHabitat,
|
||||
appt: json['appt'] as String? ?? '',
|
||||
niveau: json['niveau'] as String? ?? '',
|
||||
gpsLat: json['gps_lat'] as String,
|
||||
gpsLng: json['gps_lng'] as String,
|
||||
nomRecu: json['nom_recu'] as String? ?? '',
|
||||
remarque: json['remarque'] as String? ?? '',
|
||||
montant: json['montant'] as String,
|
||||
fkTypeReglement: fkTypeReglement,
|
||||
emailErreur: json['email_erreur'] as String? ?? '',
|
||||
nbPassages: nbPassages,
|
||||
name: json['name'] as String,
|
||||
email: json['email'] as String? ?? '',
|
||||
phone: json['phone'] as String? ?? '',
|
||||
lastSyncedAt: DateTime.now(),
|
||||
isActive: true,
|
||||
isSynced: true,
|
||||
);
|
||||
}
|
||||
|
||||
// Convertir en JSON pour l'API
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'fk_operation': fkOperation,
|
||||
'fk_sector': fkSector,
|
||||
'fk_user': fkUser,
|
||||
'fk_type': fkType,
|
||||
'fk_adresse': fkAdresse,
|
||||
'passed_at': passedAt.toIso8601String(),
|
||||
'numero': numero,
|
||||
'rue': rue,
|
||||
'rue_bis': rueBis,
|
||||
'ville': ville,
|
||||
'residence': residence,
|
||||
'fk_habitat': fkHabitat,
|
||||
'appt': appt,
|
||||
'niveau': niveau,
|
||||
'gps_lat': gpsLat,
|
||||
'gps_lng': gpsLng,
|
||||
'nom_recu': nomRecu,
|
||||
'remarque': remarque,
|
||||
'montant': montant,
|
||||
'fk_type_reglement': fkTypeReglement,
|
||||
'email_erreur': emailErreur,
|
||||
'nb_passages': nbPassages,
|
||||
'name': name,
|
||||
'email': email,
|
||||
'phone': phone,
|
||||
};
|
||||
}
|
||||
|
||||
// Copier avec de nouvelles valeurs
|
||||
PassageModel copyWith({
|
||||
int? id,
|
||||
int? fkOperation,
|
||||
int? fkSector,
|
||||
int? fkUser,
|
||||
int? fkType,
|
||||
String? fkAdresse,
|
||||
DateTime? passedAt,
|
||||
String? numero,
|
||||
String? rue,
|
||||
String? rueBis,
|
||||
String? ville,
|
||||
String? residence,
|
||||
int? fkHabitat,
|
||||
String? appt,
|
||||
String? niveau,
|
||||
String? gpsLat,
|
||||
String? gpsLng,
|
||||
String? nomRecu,
|
||||
String? remarque,
|
||||
String? montant,
|
||||
int? fkTypeReglement,
|
||||
String? emailErreur,
|
||||
int? nbPassages,
|
||||
String? name,
|
||||
String? email,
|
||||
String? phone,
|
||||
DateTime? lastSyncedAt,
|
||||
bool? isActive,
|
||||
bool? isSynced,
|
||||
}) {
|
||||
return PassageModel(
|
||||
id: id ?? this.id,
|
||||
fkOperation: fkOperation ?? this.fkOperation,
|
||||
fkSector: fkSector ?? this.fkSector,
|
||||
fkUser: fkUser ?? this.fkUser,
|
||||
fkType: fkType ?? this.fkType,
|
||||
fkAdresse: fkAdresse ?? this.fkAdresse,
|
||||
passedAt: passedAt ?? this.passedAt,
|
||||
numero: numero ?? this.numero,
|
||||
rue: rue ?? this.rue,
|
||||
rueBis: rueBis ?? this.rueBis,
|
||||
ville: ville ?? this.ville,
|
||||
residence: residence ?? this.residence,
|
||||
fkHabitat: fkHabitat ?? this.fkHabitat,
|
||||
appt: appt ?? this.appt,
|
||||
niveau: niveau ?? this.niveau,
|
||||
gpsLat: gpsLat ?? this.gpsLat,
|
||||
gpsLng: gpsLng ?? this.gpsLng,
|
||||
nomRecu: nomRecu ?? this.nomRecu,
|
||||
remarque: remarque ?? this.remarque,
|
||||
montant: montant ?? this.montant,
|
||||
fkTypeReglement: fkTypeReglement ?? this.fkTypeReglement,
|
||||
emailErreur: emailErreur ?? this.emailErreur,
|
||||
nbPassages: nbPassages ?? this.nbPassages,
|
||||
name: name ?? this.name,
|
||||
email: email ?? this.email,
|
||||
phone: phone ?? this.phone,
|
||||
lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt,
|
||||
isActive: isActive ?? this.isActive,
|
||||
isSynced: isSynced ?? this.isSynced,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PassageModel(id: $id, fkOperation: $fkOperation, fkSector: $fkSector, fkUser: $fkUser, fkType: $fkType, adresse: $fkAdresse, ville: $ville, montant: $montant)';
|
||||
}
|
||||
}
|
||||
125
flutt/lib/core/data/models/passage_model.g.dart
Normal file
125
flutt/lib/core/data/models/passage_model.g.dart
Normal file
@@ -0,0 +1,125 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'passage_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class PassageModelAdapter extends TypeAdapter<PassageModel> {
|
||||
@override
|
||||
final int typeId = 4;
|
||||
|
||||
@override
|
||||
PassageModel read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return PassageModel(
|
||||
id: fields[0] as int,
|
||||
fkOperation: fields[1] as int,
|
||||
fkSector: fields[2] as int,
|
||||
fkUser: fields[3] as int,
|
||||
fkType: fields[4] as int,
|
||||
fkAdresse: fields[5] as String,
|
||||
passedAt: fields[6] as DateTime,
|
||||
numero: fields[7] as String,
|
||||
rue: fields[8] as String,
|
||||
rueBis: fields[9] as String,
|
||||
ville: fields[10] as String,
|
||||
residence: fields[11] as String,
|
||||
fkHabitat: fields[12] as int,
|
||||
appt: fields[13] as String,
|
||||
niveau: fields[14] as String,
|
||||
gpsLat: fields[15] as String,
|
||||
gpsLng: fields[16] as String,
|
||||
nomRecu: fields[17] as String,
|
||||
remarque: fields[18] as String,
|
||||
montant: fields[19] as String,
|
||||
fkTypeReglement: fields[20] as int,
|
||||
emailErreur: fields[21] as String,
|
||||
nbPassages: fields[22] as int,
|
||||
name: fields[23] as String,
|
||||
email: fields[24] as String,
|
||||
phone: fields[25] as String,
|
||||
lastSyncedAt: fields[26] as DateTime,
|
||||
isActive: fields[27] as bool,
|
||||
isSynced: fields[28] as bool,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, PassageModel obj) {
|
||||
writer
|
||||
..writeByte(29)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.fkOperation)
|
||||
..writeByte(2)
|
||||
..write(obj.fkSector)
|
||||
..writeByte(3)
|
||||
..write(obj.fkUser)
|
||||
..writeByte(4)
|
||||
..write(obj.fkType)
|
||||
..writeByte(5)
|
||||
..write(obj.fkAdresse)
|
||||
..writeByte(6)
|
||||
..write(obj.passedAt)
|
||||
..writeByte(7)
|
||||
..write(obj.numero)
|
||||
..writeByte(8)
|
||||
..write(obj.rue)
|
||||
..writeByte(9)
|
||||
..write(obj.rueBis)
|
||||
..writeByte(10)
|
||||
..write(obj.ville)
|
||||
..writeByte(11)
|
||||
..write(obj.residence)
|
||||
..writeByte(12)
|
||||
..write(obj.fkHabitat)
|
||||
..writeByte(13)
|
||||
..write(obj.appt)
|
||||
..writeByte(14)
|
||||
..write(obj.niveau)
|
||||
..writeByte(15)
|
||||
..write(obj.gpsLat)
|
||||
..writeByte(16)
|
||||
..write(obj.gpsLng)
|
||||
..writeByte(17)
|
||||
..write(obj.nomRecu)
|
||||
..writeByte(18)
|
||||
..write(obj.remarque)
|
||||
..writeByte(19)
|
||||
..write(obj.montant)
|
||||
..writeByte(20)
|
||||
..write(obj.fkTypeReglement)
|
||||
..writeByte(21)
|
||||
..write(obj.emailErreur)
|
||||
..writeByte(22)
|
||||
..write(obj.nbPassages)
|
||||
..writeByte(23)
|
||||
..write(obj.name)
|
||||
..writeByte(24)
|
||||
..write(obj.email)
|
||||
..writeByte(25)
|
||||
..write(obj.phone)
|
||||
..writeByte(26)
|
||||
..write(obj.lastSyncedAt)
|
||||
..writeByte(27)
|
||||
..write(obj.isActive)
|
||||
..writeByte(28)
|
||||
..write(obj.isSynced);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is PassageModelAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
85
flutt/lib/core/data/models/sector_model.dart
Normal file
85
flutt/lib/core/data/models/sector_model.dart
Normal file
@@ -0,0 +1,85 @@
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'sector_model.g.dart';
|
||||
|
||||
@HiveType(typeId: 3)
|
||||
class SectorModel extends HiveObject {
|
||||
@HiveField(0)
|
||||
final int id;
|
||||
|
||||
@HiveField(1)
|
||||
final String libelle;
|
||||
|
||||
@HiveField(2)
|
||||
final String color;
|
||||
|
||||
@HiveField(3)
|
||||
final String sector;
|
||||
|
||||
SectorModel({
|
||||
required this.id,
|
||||
required this.libelle,
|
||||
required this.color,
|
||||
required this.sector,
|
||||
});
|
||||
|
||||
// Factory pour convertir depuis JSON (API)
|
||||
factory SectorModel.fromJson(Map<String, dynamic> json) {
|
||||
return SectorModel(
|
||||
id: json['id'] is String ? int.parse(json['id']) : json['id'] as int,
|
||||
libelle: json['libelle'] as String,
|
||||
color: json['color'] as String,
|
||||
sector: json['sector'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
// Convertir en JSON pour l'API
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'libelle': libelle,
|
||||
'color': color,
|
||||
'sector': sector,
|
||||
};
|
||||
}
|
||||
|
||||
// Copier avec de nouvelles valeurs
|
||||
SectorModel copyWith({
|
||||
int? id,
|
||||
String? libelle,
|
||||
String? color,
|
||||
String? sector,
|
||||
}) {
|
||||
return SectorModel(
|
||||
id: id ?? this.id,
|
||||
libelle: libelle ?? this.libelle,
|
||||
color: color ?? this.color,
|
||||
sector: sector ?? this.sector,
|
||||
);
|
||||
}
|
||||
|
||||
// Obtenir les coordonnées du secteur sous forme de liste de points
|
||||
List<List<double>> getCoordinates() {
|
||||
final List<List<double>> coordinates = [];
|
||||
|
||||
// Le format est "lat1/lng1#lat2/lng2#lat3/lng3#..."
|
||||
final List<String> points = sector.split('#');
|
||||
|
||||
for (final String point in points) {
|
||||
if (point.isEmpty) continue;
|
||||
|
||||
final List<String> latLng = point.split('/');
|
||||
if (latLng.length == 2) {
|
||||
try {
|
||||
final double lat = double.parse(latLng[0]);
|
||||
final double lng = double.parse(latLng[1]);
|
||||
coordinates.add([lat, lng]);
|
||||
} catch (e) {
|
||||
// Ignorer les points mal formatés
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return coordinates;
|
||||
}
|
||||
}
|
||||
50
flutt/lib/core/data/models/sector_model.g.dart
Normal file
50
flutt/lib/core/data/models/sector_model.g.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'sector_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class SectorModelAdapter extends TypeAdapter<SectorModel> {
|
||||
@override
|
||||
final int typeId = 3;
|
||||
|
||||
@override
|
||||
SectorModel read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return SectorModel(
|
||||
id: fields[0] as int,
|
||||
libelle: fields[1] as String,
|
||||
color: fields[2] as String,
|
||||
sector: fields[3] as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, SectorModel obj) {
|
||||
writer
|
||||
..writeByte(4)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.libelle)
|
||||
..writeByte(2)
|
||||
..write(obj.color)
|
||||
..writeByte(3)
|
||||
..write(obj.sector);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is SectorModelAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
169
flutt/lib/core/data/models/user_model.dart
Normal file
169
flutt/lib/core/data/models/user_model.dart
Normal file
@@ -0,0 +1,169 @@
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'user_model.g.dart';
|
||||
|
||||
@HiveType(typeId: 0)
|
||||
class UserModel extends HiveObject {
|
||||
@HiveField(0)
|
||||
final int id;
|
||||
|
||||
@HiveField(1)
|
||||
final String email;
|
||||
|
||||
@HiveField(2)
|
||||
String? name;
|
||||
|
||||
@HiveField(11)
|
||||
String? username;
|
||||
|
||||
@HiveField(10)
|
||||
String? firstName;
|
||||
|
||||
@HiveField(3)
|
||||
final int role;
|
||||
|
||||
@HiveField(4)
|
||||
final DateTime createdAt;
|
||||
|
||||
@HiveField(5)
|
||||
DateTime lastSyncedAt;
|
||||
|
||||
@HiveField(6)
|
||||
bool isActive;
|
||||
|
||||
@HiveField(7)
|
||||
bool isSynced;
|
||||
|
||||
@HiveField(8)
|
||||
String? sessionId;
|
||||
|
||||
@HiveField(9)
|
||||
DateTime? sessionExpiry;
|
||||
|
||||
@HiveField(12)
|
||||
String? lastPath;
|
||||
|
||||
@HiveField(13)
|
||||
String? sectName;
|
||||
|
||||
@HiveField(14)
|
||||
String? interface;
|
||||
|
||||
UserModel({
|
||||
required this.id,
|
||||
required this.email,
|
||||
this.name,
|
||||
this.username,
|
||||
this.firstName,
|
||||
required this.role,
|
||||
required this.createdAt,
|
||||
required this.lastSyncedAt,
|
||||
this.isActive = true,
|
||||
this.isSynced = false,
|
||||
this.sessionId,
|
||||
this.sessionExpiry,
|
||||
this.lastPath,
|
||||
this.sectName,
|
||||
this.interface,
|
||||
});
|
||||
|
||||
// Factory pour convertir depuis JSON (API)
|
||||
factory UserModel.fromJson(Map<String, dynamic> json) {
|
||||
// Convertir l'ID en int, qu'il soit déjà int ou string
|
||||
final dynamic rawId = json['id'];
|
||||
final int id = rawId is String ? int.parse(rawId) : rawId as int;
|
||||
|
||||
// Convertir le rôle en int, qu'il soit déjà int ou string
|
||||
final dynamic rawRole = json['role'];
|
||||
final int role = rawRole is String ? int.parse(rawRole) : rawRole as int;
|
||||
|
||||
return UserModel(
|
||||
id: id,
|
||||
email: json['email'],
|
||||
name: json['name'],
|
||||
username: json['username'],
|
||||
firstName: json['first_name'],
|
||||
role: role,
|
||||
createdAt: DateTime.parse(json['created_at']),
|
||||
lastSyncedAt: DateTime.now(),
|
||||
isActive: json['is_active'] ?? true,
|
||||
isSynced: true,
|
||||
sessionId: json['session_id'],
|
||||
sessionExpiry: json['session_expiry'] != null
|
||||
? DateTime.parse(json['session_expiry'])
|
||||
: null,
|
||||
sectName: json['sect_name'],
|
||||
interface: json['interface'],
|
||||
);
|
||||
}
|
||||
|
||||
// Convertir en JSON pour l'API
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'email': email,
|
||||
'name': name,
|
||||
'username': username,
|
||||
'first_name': firstName,
|
||||
'role': role,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'is_active': isActive,
|
||||
'session_id': sessionId,
|
||||
'session_expiry': sessionExpiry?.toIso8601String(),
|
||||
'last_path': lastPath,
|
||||
'sect_name': sectName,
|
||||
'interface': interface,
|
||||
};
|
||||
}
|
||||
|
||||
// Copier avec de nouvelles valeurs
|
||||
UserModel copyWith({
|
||||
String? email,
|
||||
String? name,
|
||||
String? username,
|
||||
String? firstName,
|
||||
int? role,
|
||||
bool? isActive,
|
||||
bool? isSynced,
|
||||
DateTime? lastSyncedAt,
|
||||
String? sessionId,
|
||||
DateTime? sessionExpiry,
|
||||
String? lastPath,
|
||||
String? sectName,
|
||||
String? interface,
|
||||
}) {
|
||||
return UserModel(
|
||||
id: this.id,
|
||||
email: email ?? this.email,
|
||||
name: name ?? this.name,
|
||||
username: username ?? this.username,
|
||||
firstName: firstName ?? this.firstName,
|
||||
role: role ?? this.role,
|
||||
createdAt: this.createdAt,
|
||||
lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt,
|
||||
isActive: isActive ?? this.isActive,
|
||||
isSynced: isSynced ?? this.isSynced,
|
||||
sessionId: sessionId ?? this.sessionId,
|
||||
sessionExpiry: sessionExpiry ?? this.sessionExpiry,
|
||||
lastPath: lastPath ?? this.lastPath,
|
||||
sectName: sectName ?? this.sectName,
|
||||
interface: interface ?? this.interface,
|
||||
);
|
||||
}
|
||||
|
||||
// Vérifier si la session est valide
|
||||
bool get hasValidSession {
|
||||
if (sessionId == null || sessionExpiry == null) {
|
||||
return false;
|
||||
}
|
||||
return sessionExpiry!.isAfter(DateTime.now());
|
||||
}
|
||||
|
||||
// Effacer les données de session
|
||||
UserModel clearSession() {
|
||||
return copyWith(
|
||||
sessionId: null,
|
||||
sessionExpiry: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
83
flutt/lib/core/data/models/user_model.g.dart
Normal file
83
flutt/lib/core/data/models/user_model.g.dart
Normal file
@@ -0,0 +1,83 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'user_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class UserModelAdapter extends TypeAdapter<UserModel> {
|
||||
@override
|
||||
final int typeId = 0;
|
||||
|
||||
@override
|
||||
UserModel read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return UserModel(
|
||||
id: fields[0] as int,
|
||||
email: fields[1] as String,
|
||||
name: fields[2] as String?,
|
||||
username: fields[11] as String?,
|
||||
firstName: fields[10] as String?,
|
||||
role: fields[3] as int,
|
||||
createdAt: fields[4] as DateTime,
|
||||
lastSyncedAt: fields[5] as DateTime,
|
||||
isActive: fields[6] as bool,
|
||||
isSynced: fields[7] as bool,
|
||||
sessionId: fields[8] as String?,
|
||||
sessionExpiry: fields[9] as DateTime?,
|
||||
lastPath: fields[12] as String?,
|
||||
sectName: fields[13] as String?,
|
||||
interface: fields[14] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, UserModel obj) {
|
||||
writer
|
||||
..writeByte(15)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.email)
|
||||
..writeByte(2)
|
||||
..write(obj.name)
|
||||
..writeByte(11)
|
||||
..write(obj.username)
|
||||
..writeByte(10)
|
||||
..write(obj.firstName)
|
||||
..writeByte(3)
|
||||
..write(obj.role)
|
||||
..writeByte(4)
|
||||
..write(obj.createdAt)
|
||||
..writeByte(5)
|
||||
..write(obj.lastSyncedAt)
|
||||
..writeByte(6)
|
||||
..write(obj.isActive)
|
||||
..writeByte(7)
|
||||
..write(obj.isSynced)
|
||||
..writeByte(8)
|
||||
..write(obj.sessionId)
|
||||
..writeByte(9)
|
||||
..write(obj.sessionExpiry)
|
||||
..writeByte(12)
|
||||
..write(obj.lastPath)
|
||||
..writeByte(13)
|
||||
..write(obj.sectName)
|
||||
..writeByte(14)
|
||||
..write(obj.interface);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is UserModelAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
208
flutt/lib/core/repositories/membre_repository.dart
Normal file
208
flutt/lib/core/repositories/membre_repository.dart
Normal file
@@ -0,0 +1,208 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||
import 'package:geosector_app/core/services/api_service.dart';
|
||||
import 'package:geosector_app/core/data/models/membre_model.dart';
|
||||
|
||||
class MembreRepository extends ChangeNotifier {
|
||||
// Utilisation de getters lazy pour n'accéder à la boîte que lorsque nécessaire
|
||||
Box<MembreModel> get _membreBox =>
|
||||
Hive.box<MembreModel>(AppKeys.membresBoxName);
|
||||
|
||||
final ApiService _apiService;
|
||||
bool _isLoading = false;
|
||||
|
||||
MembreRepository(this._apiService);
|
||||
|
||||
// Getters
|
||||
bool get isLoading => _isLoading;
|
||||
List<MembreModel> get membres => getAllMembres();
|
||||
|
||||
// Méthode pour vérifier si une boîte est ouverte et l'ouvrir si nécessaire
|
||||
Future<void> _ensureBoxIsOpen() async {
|
||||
try {
|
||||
if (!Hive.isBoxOpen(AppKeys.membresBoxName)) {
|
||||
debugPrint('Ouverture de la boîte ${AppKeys.membresBoxName}...');
|
||||
await Hive.openBox<MembreModel>(AppKeys.membresBoxName);
|
||||
debugPrint('Boîte ${AppKeys.membresBoxName} ouverte avec succès');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint(
|
||||
'Erreur lors de l\'ouverture de la boîte ${AppKeys.membresBoxName}: $e');
|
||||
throw Exception(
|
||||
'Impossible d\'ouvrir la boîte ${AppKeys.membresBoxName}: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Récupérer tous les membres
|
||||
List<MembreModel> getAllMembres() {
|
||||
try {
|
||||
return _membreBox.values.toList();
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la récupération des membres: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Récupérer un membre par son ID
|
||||
MembreModel? getMembreById(int id) {
|
||||
try {
|
||||
return _membreBox.get(id);
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la récupération du membre: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Créer ou mettre à jour un membre
|
||||
Future<MembreModel> saveMembre(MembreModel membre) async {
|
||||
await _ensureBoxIsOpen();
|
||||
await _membreBox.put(membre.id, membre);
|
||||
notifyListeners();
|
||||
return membre;
|
||||
}
|
||||
|
||||
// Supprimer un membre
|
||||
Future<void> deleteMembre(int id) async {
|
||||
await _ensureBoxIsOpen();
|
||||
await _membreBox.delete(id);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Récupérer les membres depuis l'API (uniquement pour l'interface admin)
|
||||
Future<List<MembreModel>> fetchMembresFromApi() async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final hasConnection = await _apiService.hasInternetConnection();
|
||||
if (!hasConnection) {
|
||||
debugPrint(
|
||||
'Pas de connexion Internet, utilisation des données locales');
|
||||
return getAllMembres();
|
||||
}
|
||||
|
||||
// Endpoint à adapter selon votre API
|
||||
final response = await _apiService.get('/membres');
|
||||
final List<dynamic> membresData = response.data['membres'];
|
||||
|
||||
// Vider la boîte avant d'ajouter les nouveaux membres
|
||||
await _ensureBoxIsOpen();
|
||||
await _membreBox.clear();
|
||||
|
||||
final List<MembreModel> membres = [];
|
||||
for (var membreData in membresData) {
|
||||
try {
|
||||
final membre = MembreModel.fromJson(membreData);
|
||||
await _membreBox.put(membre.id, membre);
|
||||
membres.add(membre);
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors du traitement d\'un membre: $e');
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
return membres;
|
||||
} catch (e) {
|
||||
debugPrint(
|
||||
'Erreur lors de la récupération des membres depuis l\'API: $e');
|
||||
return getAllMembres();
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Créer un membre via l'API
|
||||
Future<MembreModel?> createMembreViaApi(MembreModel membre) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final hasConnection = await _apiService.hasInternetConnection();
|
||||
if (!hasConnection) {
|
||||
debugPrint('Pas de connexion Internet, impossible de créer le membre');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Endpoint à adapter selon votre API
|
||||
final response =
|
||||
await _apiService.post('/membres', data: membre.toJson());
|
||||
final membreData = response.data['membre'];
|
||||
|
||||
final newMembre = MembreModel.fromJson(membreData);
|
||||
await saveMembre(newMembre);
|
||||
|
||||
return newMembre;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la création du membre via l\'API: $e');
|
||||
return null;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Mettre à jour un membre via l'API
|
||||
Future<MembreModel?> updateMembreViaApi(MembreModel membre) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final hasConnection = await _apiService.hasInternetConnection();
|
||||
if (!hasConnection) {
|
||||
debugPrint(
|
||||
'Pas de connexion Internet, impossible de mettre à jour le membre');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Endpoint à adapter selon votre API
|
||||
final response =
|
||||
await _apiService.put('/membres/${membre.id}', data: membre.toJson());
|
||||
final membreData = response.data['membre'];
|
||||
|
||||
final updatedMembre = MembreModel.fromJson(membreData);
|
||||
await saveMembre(updatedMembre);
|
||||
|
||||
return updatedMembre;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la mise à jour du membre via l\'API: $e');
|
||||
return null;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Supprimer un membre via l'API
|
||||
Future<bool> deleteMembreViaApi(int id) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final hasConnection = await _apiService.hasInternetConnection();
|
||||
if (!hasConnection) {
|
||||
debugPrint(
|
||||
'Pas de connexion Internet, impossible de supprimer le membre');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Endpoint à adapter selon votre API
|
||||
await _apiService.delete('/membres/$id');
|
||||
|
||||
// Supprimer localement
|
||||
await deleteMembre(id);
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la suppression du membre via l\'API: $e');
|
||||
return false;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
215
flutt/lib/core/repositories/operation_repository.dart
Normal file
215
flutt/lib/core/repositories/operation_repository.dart
Normal file
@@ -0,0 +1,215 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:geosector_app/core/data/models/operation_model.dart';
|
||||
import 'package:geosector_app/core/services/api_service.dart';
|
||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||
|
||||
class OperationRepository extends ChangeNotifier {
|
||||
// Utiliser un getter lazy pour n'accéder à la boîte que lorsque nécessaire
|
||||
// et vérifier qu'elle est ouverte avant accès
|
||||
Box<OperationModel> get _operationBox {
|
||||
_ensureBoxIsOpen();
|
||||
return Hive.box<OperationModel>(AppKeys.operationsBoxName);
|
||||
}
|
||||
|
||||
// Méthode pour vérifier si la boîte est ouverte et l'ouvrir si nécessaire
|
||||
Future<void> _ensureBoxIsOpen() async {
|
||||
final boxName = AppKeys.operationsBoxName;
|
||||
if (!Hive.isBoxOpen(boxName)) {
|
||||
debugPrint('Ouverture de la boîte $boxName dans OperationRepository...');
|
||||
await Hive.openBox<OperationModel>(boxName);
|
||||
}
|
||||
}
|
||||
final ApiService _apiService;
|
||||
|
||||
bool _isLoading = false;
|
||||
|
||||
OperationRepository(this._apiService);
|
||||
|
||||
// Getters
|
||||
bool get isLoading => _isLoading;
|
||||
List<OperationModel> get operations => getAllOperations();
|
||||
|
||||
// Récupérer toutes les opérations
|
||||
List<OperationModel> getAllOperations() {
|
||||
return _operationBox.values.toList();
|
||||
}
|
||||
|
||||
// Récupérer une opération par son ID
|
||||
OperationModel? getOperationById(int id) {
|
||||
return _operationBox.get(id);
|
||||
}
|
||||
|
||||
// Sauvegarder une opération
|
||||
Future<void> saveOperation(OperationModel operation) async {
|
||||
await _operationBox.put(operation.id, operation);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Supprimer une opération
|
||||
Future<void> deleteOperation(int id) async {
|
||||
await _operationBox.delete(id);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Créer ou mettre à jour des opérations à partir des données de l'API
|
||||
Future<void> processOperationsFromApi(List<dynamic> operationsData) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
for (var operationData in operationsData) {
|
||||
final operationJson = operationData as Map<String, dynamic>;
|
||||
final operationId = operationJson['id'] is String
|
||||
? int.parse(operationJson['id'])
|
||||
: operationJson['id'] as int;
|
||||
|
||||
// Vérifier si l'opération existe déjà
|
||||
OperationModel? existingOperation = getOperationById(operationId);
|
||||
|
||||
if (existingOperation == null) {
|
||||
// Créer une nouvelle opération
|
||||
final newOperation = OperationModel.fromJson(operationJson);
|
||||
await saveOperation(newOperation);
|
||||
} else {
|
||||
// Mettre à jour l'opération existante
|
||||
final updatedOperation = existingOperation.copyWith(
|
||||
name: operationJson['name'],
|
||||
dateDebut: DateTime.parse(operationJson['date_deb']),
|
||||
dateFin: DateTime.parse(operationJson['date_fin']),
|
||||
lastSyncedAt: DateTime.now(),
|
||||
isSynced: true,
|
||||
);
|
||||
await saveOperation(updatedOperation);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors du traitement des opérations: $e');
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Créer une opération
|
||||
Future<bool> createOperation(String name, DateTime dateDebut, DateTime dateFin) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
// Préparer les données pour l'API
|
||||
final Map<String, dynamic> data = {
|
||||
'name': name,
|
||||
'date_deb': dateDebut.toIso8601String().split('T')[0], // Format YYYY-MM-DD
|
||||
'date_fin': dateFin.toIso8601String().split('T')[0], // Format YYYY-MM-DD
|
||||
};
|
||||
|
||||
// Appeler l'API pour créer l'opération
|
||||
final response = await _apiService.post('/operations', data: data);
|
||||
|
||||
if (response.statusCode == 201 || response.statusCode == 200) {
|
||||
// Récupérer l'ID de la nouvelle opération
|
||||
final operationId = response.data['id'] is String
|
||||
? int.parse(response.data['id'])
|
||||
: response.data['id'] as int;
|
||||
|
||||
// Créer l'opération localement
|
||||
final newOperation = OperationModel(
|
||||
id: operationId,
|
||||
name: name,
|
||||
dateDebut: dateDebut,
|
||||
dateFin: dateFin,
|
||||
lastSyncedAt: DateTime.now(),
|
||||
isActive: true,
|
||||
isSynced: true,
|
||||
);
|
||||
|
||||
await saveOperation(newOperation);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la création de l\'opération: $e');
|
||||
return false;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Mettre à jour une opération
|
||||
Future<bool> updateOperation(int id, {String? name, DateTime? dateDebut, DateTime? dateFin, bool? isActive}) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
// Récupérer l'opération existante
|
||||
final existingOperation = getOperationById(id);
|
||||
if (existingOperation == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Préparer les données pour l'API
|
||||
final Map<String, dynamic> data = {
|
||||
'id': id,
|
||||
'name': name ?? existingOperation.name,
|
||||
'date_deb': (dateDebut ?? existingOperation.dateDebut).toIso8601String().split('T')[0],
|
||||
'date_fin': (dateFin ?? existingOperation.dateFin).toIso8601String().split('T')[0],
|
||||
'is_active': isActive ?? existingOperation.isActive,
|
||||
};
|
||||
|
||||
// Appeler l'API pour mettre à jour l'opération
|
||||
final response = await _apiService.put('/operations/$id', data: data);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// Mettre à jour l'opération localement
|
||||
final updatedOperation = existingOperation.copyWith(
|
||||
name: name,
|
||||
dateDebut: dateDebut,
|
||||
dateFin: dateFin,
|
||||
isActive: isActive,
|
||||
lastSyncedAt: DateTime.now(),
|
||||
isSynced: true,
|
||||
);
|
||||
|
||||
await saveOperation(updatedOperation);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la mise à jour de l\'opération: $e');
|
||||
return false;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Supprimer une opération via l'API
|
||||
Future<bool> deleteOperationViaApi(int id) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
// Appeler l'API pour supprimer l'opération
|
||||
final response = await _apiService.delete('/operations/$id');
|
||||
|
||||
if (response.statusCode == 200 || response.statusCode == 204) {
|
||||
// Supprimer l'opération localement
|
||||
await deleteOperation(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la suppression de l\'opération: $e');
|
||||
return false;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
381
flutt/lib/core/repositories/passage_repository.dart
Normal file
381
flutt/lib/core/repositories/passage_repository.dart
Normal file
@@ -0,0 +1,381 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.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/constants/app_keys.dart';
|
||||
|
||||
class PassageRepository extends ChangeNotifier {
|
||||
// Utiliser un getter lazy pour n'accéder à la boîte que lorsque nécessaire
|
||||
// et vérifier qu'elle est ouverte avant accès
|
||||
Box<PassageModel> get _passageBox {
|
||||
_ensureBoxIsOpen();
|
||||
return Hive.box<PassageModel>(AppKeys.passagesBoxName);
|
||||
}
|
||||
|
||||
// Méthode pour vérifier si la boîte est ouverte et l'ouvrir si nécessaire
|
||||
Future<void> _ensureBoxIsOpen() async {
|
||||
final boxName = AppKeys.passagesBoxName;
|
||||
if (!Hive.isBoxOpen(boxName)) {
|
||||
debugPrint('Ouverture de la boîte $boxName dans PassageRepository...');
|
||||
await Hive.openBox<PassageModel>(boxName);
|
||||
}
|
||||
}
|
||||
final ApiService _apiService;
|
||||
|
||||
bool _isLoading = false;
|
||||
|
||||
PassageRepository(this._apiService);
|
||||
|
||||
// Getters
|
||||
bool get isLoading => _isLoading;
|
||||
List<PassageModel> get passages => getAllPassages();
|
||||
|
||||
// Récupérer tous les passages
|
||||
List<PassageModel> getAllPassages() {
|
||||
return _passageBox.values.toList();
|
||||
}
|
||||
|
||||
// Récupérer un passage par son ID
|
||||
PassageModel? getPassageById(int id) {
|
||||
return _passageBox.get(id);
|
||||
}
|
||||
|
||||
// Récupérer les passages par secteur
|
||||
List<PassageModel> getPassagesBySector(int sectorId) {
|
||||
return _passageBox.values
|
||||
.where((passage) => passage.fkSector == sectorId)
|
||||
.toList();
|
||||
}
|
||||
|
||||
// Récupérer les passages par opération
|
||||
List<PassageModel> getPassagesByOperation(int operationId) {
|
||||
return _passageBox.values
|
||||
.where((passage) => passage.fkOperation == operationId)
|
||||
.toList();
|
||||
}
|
||||
|
||||
// Récupérer les passages par type
|
||||
List<PassageModel> getPassagesByType(int typeId) {
|
||||
return _passageBox.values
|
||||
.where((passage) => passage.fkType == typeId)
|
||||
.toList();
|
||||
}
|
||||
|
||||
// Récupérer les passages par type de règlement
|
||||
List<PassageModel> getPassagesByPaymentType(int paymentTypeId) {
|
||||
return _passageBox.values
|
||||
.where((passage) => passage.fkTypeReglement == paymentTypeId)
|
||||
.toList();
|
||||
}
|
||||
|
||||
// Sauvegarder un passage
|
||||
Future<void> savePassage(PassageModel passage) async {
|
||||
await _passageBox.put(passage.id, passage);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Supprimer un passage
|
||||
Future<void> deletePassage(int id) async {
|
||||
await _passageBox.delete(id);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Traiter les passages reçus de l'API
|
||||
Future<void> processPassagesFromApi(List<dynamic> passagesData) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
for (var passageData in passagesData) {
|
||||
final passageJson = passageData as Map<String, dynamic>;
|
||||
final passageId = passageJson['id'] is String
|
||||
? int.parse(passageJson['id'])
|
||||
: passageJson['id'] as int;
|
||||
|
||||
// Vérifier si le passage existe déjà
|
||||
PassageModel? existingPassage = getPassageById(passageId);
|
||||
|
||||
if (existingPassage == null) {
|
||||
// Créer un nouveau passage
|
||||
final newPassage = PassageModel.fromJson(passageJson);
|
||||
await savePassage(newPassage);
|
||||
} else {
|
||||
// Mettre à jour le passage existant avec les nouvelles données
|
||||
final updatedPassage = PassageModel.fromJson(passageJson).copyWith(
|
||||
lastSyncedAt: DateTime.now(),
|
||||
isActive: existingPassage.isActive,
|
||||
isSynced: true,
|
||||
);
|
||||
await savePassage(updatedPassage);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors du traitement des passages: $e');
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Créer un nouveau passage
|
||||
Future<bool> createPassage({
|
||||
required int fkOperation,
|
||||
required int fkSector,
|
||||
required int fkUser,
|
||||
required int fkType,
|
||||
required String fkAdresse,
|
||||
required DateTime passedAt,
|
||||
required String numero,
|
||||
required String rue,
|
||||
String rueBis = '',
|
||||
required String ville,
|
||||
String residence = '',
|
||||
required int fkHabitat,
|
||||
String appt = '',
|
||||
String niveau = '',
|
||||
required String gpsLat,
|
||||
required String gpsLng,
|
||||
String nomRecu = '',
|
||||
String remarque = '',
|
||||
required String montant,
|
||||
required int fkTypeReglement,
|
||||
String name = '',
|
||||
String email = '',
|
||||
String phone = '',
|
||||
}) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
// Préparer les données pour l'API
|
||||
final Map<String, dynamic> data = {
|
||||
'fk_operation': fkOperation,
|
||||
'fk_sector': fkSector,
|
||||
'fk_user': fkUser,
|
||||
'fk_type': fkType,
|
||||
'fk_adresse': fkAdresse,
|
||||
'passed_at': passedAt.toIso8601String(),
|
||||
'numero': numero,
|
||||
'rue': rue,
|
||||
'rue_bis': rueBis,
|
||||
'ville': ville,
|
||||
'residence': residence,
|
||||
'fk_habitat': fkHabitat,
|
||||
'appt': appt,
|
||||
'niveau': niveau,
|
||||
'gps_lat': gpsLat,
|
||||
'gps_lng': gpsLng,
|
||||
'nom_recu': nomRecu,
|
||||
'remarque': remarque,
|
||||
'montant': montant,
|
||||
'fk_type_reglement': fkTypeReglement,
|
||||
'name': name,
|
||||
'email': email,
|
||||
'phone': phone,
|
||||
};
|
||||
|
||||
// Appeler l'API pour créer le passage
|
||||
final response = await _apiService.post('/passages', data: data);
|
||||
|
||||
if (response.statusCode == 201 || response.statusCode == 200) {
|
||||
// Récupérer l'ID du nouveau passage
|
||||
final passageId = response.data['id'] is String
|
||||
? int.parse(response.data['id'])
|
||||
: response.data['id'] as int;
|
||||
|
||||
// Créer le modèle local
|
||||
final newPassage = PassageModel(
|
||||
id: passageId,
|
||||
fkOperation: fkOperation,
|
||||
fkSector: fkSector,
|
||||
fkUser: fkUser,
|
||||
fkType: fkType,
|
||||
fkAdresse: fkAdresse,
|
||||
passedAt: passedAt,
|
||||
numero: numero,
|
||||
rue: rue,
|
||||
rueBis: rueBis,
|
||||
ville: ville,
|
||||
residence: residence,
|
||||
fkHabitat: fkHabitat,
|
||||
appt: appt,
|
||||
niveau: niveau,
|
||||
gpsLat: gpsLat,
|
||||
gpsLng: gpsLng,
|
||||
nomRecu: nomRecu,
|
||||
remarque: remarque,
|
||||
montant: montant,
|
||||
fkTypeReglement: fkTypeReglement,
|
||||
nbPassages: 1, // Par défaut pour un nouveau passage
|
||||
name: name,
|
||||
email: email,
|
||||
phone: phone,
|
||||
lastSyncedAt: DateTime.now(),
|
||||
isActive: true,
|
||||
isSynced: true,
|
||||
);
|
||||
|
||||
await savePassage(newPassage);
|
||||
return true;
|
||||
} else {
|
||||
debugPrint('Erreur lors de la création du passage: ${response.statusMessage}');
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la création du passage: $e');
|
||||
return false;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Mettre à jour un passage existant
|
||||
Future<bool> updatePassage(PassageModel passage) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
// Préparer les données pour l'API
|
||||
final Map<String, dynamic> data = passage.toJson();
|
||||
|
||||
// Appeler l'API pour mettre à jour le passage
|
||||
final response = await _apiService.put('/passages/${passage.id}', data: data);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// Mettre à jour le modèle local
|
||||
final updatedPassage = passage.copyWith(
|
||||
lastSyncedAt: DateTime.now(),
|
||||
isSynced: true,
|
||||
);
|
||||
|
||||
await savePassage(updatedPassage);
|
||||
return true;
|
||||
} else {
|
||||
debugPrint('Erreur lors de la mise à jour du passage: ${response.statusMessage}');
|
||||
|
||||
// Marquer comme non synchronisé mais sauvegarder localement
|
||||
final updatedPassage = passage.copyWith(
|
||||
lastSyncedAt: DateTime.now(),
|
||||
isSynced: false,
|
||||
);
|
||||
|
||||
await savePassage(updatedPassage);
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la mise à jour du passage: $e');
|
||||
|
||||
// Marquer comme non synchronisé mais sauvegarder localement
|
||||
final updatedPassage = passage.copyWith(
|
||||
lastSyncedAt: DateTime.now(),
|
||||
isSynced: false,
|
||||
);
|
||||
|
||||
await savePassage(updatedPassage);
|
||||
return false;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Synchroniser tous les passages non synchronisés
|
||||
Future<void> syncUnsyncedPassages() async {
|
||||
try {
|
||||
final hasConnection = await _apiService.hasInternetConnection();
|
||||
|
||||
if (!hasConnection) {
|
||||
return;
|
||||
}
|
||||
|
||||
final unsyncedPassages = _passageBox.values.where((passage) => !passage.isSynced).toList();
|
||||
|
||||
if (unsyncedPassages.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
for (final passage in unsyncedPassages) {
|
||||
try {
|
||||
if (passage.id < 0) {
|
||||
// Nouveau passage créé localement, à envoyer à l'API
|
||||
await createPassage(
|
||||
fkOperation: passage.fkOperation,
|
||||
fkSector: passage.fkSector,
|
||||
fkUser: passage.fkUser,
|
||||
fkType: passage.fkType,
|
||||
fkAdresse: passage.fkAdresse,
|
||||
passedAt: passage.passedAt,
|
||||
numero: passage.numero,
|
||||
rue: passage.rue,
|
||||
rueBis: passage.rueBis,
|
||||
ville: passage.ville,
|
||||
residence: passage.residence,
|
||||
fkHabitat: passage.fkHabitat,
|
||||
appt: passage.appt,
|
||||
niveau: passage.niveau,
|
||||
gpsLat: passage.gpsLat,
|
||||
gpsLng: passage.gpsLng,
|
||||
nomRecu: passage.nomRecu,
|
||||
remarque: passage.remarque,
|
||||
montant: passage.montant,
|
||||
fkTypeReglement: passage.fkTypeReglement,
|
||||
name: passage.name,
|
||||
email: passage.email,
|
||||
phone: passage.phone,
|
||||
);
|
||||
|
||||
// Supprimer l'ancien passage avec ID temporaire
|
||||
await deletePassage(passage.id);
|
||||
} else {
|
||||
// Passage existant à mettre à jour
|
||||
await updatePassage(passage);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la synchronisation du passage ${passage.id}: $e');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la synchronisation des passages: $e');
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Récupérer les passages depuis l'API
|
||||
Future<void> fetchPassages() async {
|
||||
try {
|
||||
final hasConnection = await _apiService.hasInternetConnection();
|
||||
|
||||
if (!hasConnection) {
|
||||
return;
|
||||
}
|
||||
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
final response = await _apiService.get('/passages');
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final List<dynamic> passagesData = response.data;
|
||||
await processPassagesFromApi(passagesData);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la récupération des passages: $e');
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Vider tous les passages
|
||||
Future<void> clearAllPassages() async {
|
||||
await _passageBox.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
149
flutt/lib/core/repositories/sector_repository.dart
Normal file
149
flutt/lib/core/repositories/sector_repository.dart
Normal file
@@ -0,0 +1,149 @@
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:geosector_app/core/data/models/sector_model.dart';
|
||||
import 'package:geosector_app/core/services/api_service.dart';
|
||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||
|
||||
class SectorRepository {
|
||||
final ApiService _apiService;
|
||||
|
||||
SectorRepository(this._apiService);
|
||||
|
||||
// Utiliser un getter lazy pour n'accéder à la boîte que lorsque nécessaire
|
||||
// et vérifier qu'elle est ouverte avant accès
|
||||
Box<SectorModel> get _sectorsBox {
|
||||
_ensureBoxIsOpen();
|
||||
return Hive.box<SectorModel>(AppKeys.sectorsBoxName);
|
||||
}
|
||||
|
||||
// Méthode pour vérifier si la boîte est ouverte et l'ouvrir si nécessaire
|
||||
Future<void> _ensureBoxIsOpen() async {
|
||||
final boxName = AppKeys.sectorsBoxName;
|
||||
if (!Hive.isBoxOpen(boxName)) {
|
||||
print('Ouverture de la boîte $boxName dans SectorRepository...');
|
||||
await Hive.openBox<SectorModel>(boxName);
|
||||
}
|
||||
}
|
||||
|
||||
// Récupérer tous les secteurs depuis la base de données locale
|
||||
List<SectorModel> getAllSectors() {
|
||||
return _sectorsBox.values.toList();
|
||||
}
|
||||
|
||||
// Récupérer un secteur par son ID
|
||||
SectorModel? getSectorById(int id) {
|
||||
try {
|
||||
return _sectorsBox.values.firstWhere(
|
||||
(sector) => sector.id == id,
|
||||
);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Sauvegarder les secteurs dans la base de données locale
|
||||
Future<void> saveSectors(List<SectorModel> sectors) async {
|
||||
// Vider la box avant d'ajouter les nouveaux secteurs
|
||||
await _sectorsBox.clear();
|
||||
|
||||
// Ajouter les nouveaux secteurs
|
||||
for (final sector in sectors) {
|
||||
await _sectorsBox.put(sector.id, sector);
|
||||
}
|
||||
}
|
||||
|
||||
// Ajouter ou mettre à jour un secteur
|
||||
Future<void> saveSector(SectorModel sector) async {
|
||||
await _sectorsBox.put(sector.id, sector);
|
||||
}
|
||||
|
||||
// Supprimer un secteur
|
||||
Future<void> deleteSector(int id) async {
|
||||
await _sectorsBox.delete(id);
|
||||
}
|
||||
|
||||
// Récupérer les secteurs depuis l'API
|
||||
Future<List<SectorModel>> fetchSectorsFromApi() async {
|
||||
try {
|
||||
final response = await _apiService.get(AppKeys.sectorsEndpoint);
|
||||
final Map<String, dynamic> responseData = response as Map<String, dynamic>;
|
||||
|
||||
if (responseData['status'] == 'success' && responseData['sectors'] != null) {
|
||||
final List<dynamic> sectorsJson = responseData['sectors'];
|
||||
final List<SectorModel> sectors = sectorsJson
|
||||
.map((json) => SectorModel.fromJson(json))
|
||||
.toList();
|
||||
|
||||
// Sauvegarder les secteurs localement
|
||||
await saveSectors(sectors);
|
||||
|
||||
return sectors;
|
||||
}
|
||||
|
||||
return [];
|
||||
} catch (e) {
|
||||
// En cas d'erreur, retourner les secteurs locaux
|
||||
return getAllSectors();
|
||||
}
|
||||
}
|
||||
|
||||
// Créer un nouveau secteur via l'API
|
||||
Future<SectorModel?> createSector(SectorModel sector) async {
|
||||
try {
|
||||
final response = await _apiService.post(
|
||||
AppKeys.sectorsEndpoint,
|
||||
data: sector.toJson(),
|
||||
);
|
||||
final Map<String, dynamic> responseData = response as Map<String, dynamic>;
|
||||
|
||||
if (responseData['status'] == 'success' && responseData['sector'] != null) {
|
||||
final SectorModel newSector = SectorModel.fromJson(responseData['sector']);
|
||||
await saveSector(newSector);
|
||||
return newSector;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Mettre à jour un secteur via l'API
|
||||
Future<SectorModel?> updateSector(SectorModel sector) async {
|
||||
try {
|
||||
final response = await _apiService.put(
|
||||
'${AppKeys.sectorsEndpoint}/${sector.id}',
|
||||
data: sector.toJson(),
|
||||
);
|
||||
final Map<String, dynamic> responseData = response as Map<String, dynamic>;
|
||||
|
||||
if (responseData['status'] == 'success' && responseData['sector'] != null) {
|
||||
final SectorModel updatedSector = SectorModel.fromJson(responseData['sector']);
|
||||
await saveSector(updatedSector);
|
||||
return updatedSector;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Supprimer un secteur via l'API
|
||||
Future<bool> deleteSectorFromApi(int id) async {
|
||||
try {
|
||||
final response = await _apiService.delete(
|
||||
'${AppKeys.sectorsEndpoint}/$id',
|
||||
);
|
||||
final Map<String, dynamic> responseData = response as Map<String, dynamic>;
|
||||
|
||||
if (responseData['status'] == 'success') {
|
||||
await deleteSector(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
958
flutt/lib/core/repositories/user_repository.dart
Normal file
958
flutt/lib/core/repositories/user_repository.dart
Normal file
@@ -0,0 +1,958 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:js' as js;
|
||||
import 'package:geosector_app/core/services/hive_web_fix.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.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/sync_service.dart';
|
||||
import 'package:geosector_app/core/data/models/user_model.dart';
|
||||
import 'package:geosector_app/core/data/models/operation_model.dart';
|
||||
import 'package:geosector_app/core/data/models/sector_model.dart';
|
||||
import 'package:geosector_app/core/data/models/passage_model.dart';
|
||||
import 'package:geosector_app/core/data/models/membre_model.dart';
|
||||
import 'package:geosector_app/core/repositories/operation_repository.dart';
|
||||
import 'package:geosector_app/core/repositories/sector_repository.dart';
|
||||
import 'package:geosector_app/core/repositories/passage_repository.dart';
|
||||
import 'package:geosector_app/chat/models/conversation_model.dart';
|
||||
import 'package:geosector_app/chat/models/message_model.dart';
|
||||
|
||||
class UserRepository extends ChangeNotifier {
|
||||
// Utilisation de getters lazy pour n'accéder aux boîtes que lorsque nécessaire
|
||||
Box<UserModel> get _userBox => Hive.box<UserModel>(AppKeys.usersBoxName);
|
||||
|
||||
// Getters pour les autres boîtes qui vérifient si elles sont ouvertes avant accès
|
||||
Box<OperationModel> get _operationBox {
|
||||
_ensureBoxIsOpen(AppKeys.operationsBoxName);
|
||||
return Hive.box<OperationModel>(AppKeys.operationsBoxName);
|
||||
}
|
||||
|
||||
Box<SectorModel> get _sectorBox {
|
||||
_ensureBoxIsOpen(AppKeys.sectorsBoxName);
|
||||
return Hive.box<SectorModel>(AppKeys.sectorsBoxName);
|
||||
}
|
||||
|
||||
// Méthode pour initialiser les boîtes après connexion
|
||||
Future<void> _initializeBoxes() async {
|
||||
debugPrint('Initialisation des boîtes Hive nécessaires...');
|
||||
await _ensureBoxIsOpen(AppKeys.operationsBoxName);
|
||||
await _ensureBoxIsOpen(AppKeys.sectorsBoxName);
|
||||
await _ensureBoxIsOpen(AppKeys.passagesBoxName);
|
||||
await _ensureBoxIsOpen(AppKeys.membresBoxName);
|
||||
// Les boîtes de chat sont déjà initialisées au démarrage
|
||||
await _ensureBoxIsOpen(AppKeys.chatConversationsBoxName);
|
||||
await _ensureBoxIsOpen(AppKeys.chatMessagesBoxName);
|
||||
debugPrint('Toutes les boîtes Hive sont maintenant ouvertes');
|
||||
}
|
||||
|
||||
final ApiService _apiService;
|
||||
final SyncService? _syncService;
|
||||
final OperationRepository? _operationRepository;
|
||||
final SectorRepository? _sectorRepository;
|
||||
final PassageRepository? _passageRepository;
|
||||
|
||||
bool _isLoading = false;
|
||||
|
||||
UserRepository(this._apiService,
|
||||
{SyncService? syncService,
|
||||
OperationRepository? operationRepository,
|
||||
SectorRepository? sectorRepository,
|
||||
PassageRepository? passageRepository})
|
||||
: _syncService = syncService,
|
||||
_operationRepository = operationRepository,
|
||||
_sectorRepository = sectorRepository,
|
||||
_passageRepository = passageRepository {
|
||||
// Initialiser la session si un utilisateur est déjà connecté
|
||||
final currentUser = getCurrentUser();
|
||||
if (currentUser != null && currentUser.sessionId != null) {
|
||||
setSessionId(currentUser.sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
bool get isLoading => _isLoading;
|
||||
bool get isLoggedIn => getCurrentUser() != null;
|
||||
// Vérifie si l'utilisateur a un rôle administrateur (2, 4 ou 9)
|
||||
bool isAdmin() {
|
||||
final user = getCurrentUser();
|
||||
if (user == null) return false;
|
||||
|
||||
final String interface = user.interface ?? 'user';
|
||||
return interface == 'admin';
|
||||
}
|
||||
|
||||
int? get userId => getCurrentUser()?.id;
|
||||
UserModel? get currentUser => getCurrentUser();
|
||||
|
||||
// Récupérer l'utilisateur actuellement connecté
|
||||
UserModel? getCurrentUser() {
|
||||
try {
|
||||
// Chercher un utilisateur avec une session active
|
||||
final activeUsers = _userBox.values
|
||||
.where((user) =>
|
||||
user.sessionId != null && // Vérifier que sessionId n'est pas null
|
||||
user.sessionId!
|
||||
.isNotEmpty && // Vérifier que sessionId n'est pas vide
|
||||
user.sessionExpiry != null &&
|
||||
user.sessionExpiry!.isAfter(DateTime.now()))
|
||||
.toList();
|
||||
|
||||
return activeUsers.isNotEmpty ? activeUsers.first : null;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la récupération de l\'utilisateur actuel: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Mettre à jour le chemin de la page actuelle pour l'utilisateur connecté
|
||||
Future<void> updateLastPath(String path) async {
|
||||
final currentUser = getCurrentUser();
|
||||
if (currentUser != null) {
|
||||
final updatedUser = currentUser.copyWith(lastPath: path);
|
||||
await saveUser(updatedUser);
|
||||
}
|
||||
}
|
||||
|
||||
// Récupérer le dernier chemin visité par l'utilisateur
|
||||
String? getLastPath() {
|
||||
final currentUser = getCurrentUser();
|
||||
return currentUser?.lastPath;
|
||||
}
|
||||
|
||||
// Configurer la session dans l'API
|
||||
void setSessionId(String? sessionId) {
|
||||
_apiService.setSessionId(sessionId);
|
||||
}
|
||||
|
||||
// Login API PHP
|
||||
Future<Map<String, dynamic>> loginAPI(String username, String password,
|
||||
{String type = 'admin'}) async {
|
||||
try {
|
||||
return await _apiService.login(username, password, type: type);
|
||||
} catch (e) {
|
||||
debugPrint('Erreur login API: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// Register API PHP - Uniquement pour les administrateurs
|
||||
Future<Map<String, dynamic>> registerAPI(String email, String name,
|
||||
String amicaleName, String postalCode, String cityName) async {
|
||||
try {
|
||||
final Map<String, dynamic> data = {
|
||||
'email': email,
|
||||
'name': name,
|
||||
'amicale_name': amicaleName,
|
||||
'postal_code': postalCode,
|
||||
'city_name': cityName
|
||||
};
|
||||
|
||||
final response =
|
||||
await _apiService.post(AppKeys.registerEndpoint, data: data);
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur register API: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// Logout API PHP
|
||||
Future<void> logoutAPI() async {
|
||||
try {
|
||||
await _apiService.logout();
|
||||
} catch (e) {
|
||||
debugPrint('Erreur logout API: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode d'inscription (uniquement pour les administrateurs)
|
||||
Future<bool> register(String email, String password, String name,
|
||||
String amicaleName, String postalCode, String cityName) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
// Enregistrer l'administrateur via l'API
|
||||
final apiResult =
|
||||
await registerAPI(email, name, amicaleName, postalCode, cityName);
|
||||
|
||||
// Créer l'administrateur local
|
||||
final int userId = apiResult['user_id'] is String
|
||||
? int.parse(apiResult['user_id'])
|
||||
: apiResult['user_id'];
|
||||
final now = DateTime.now();
|
||||
final newAdmin = UserModel(
|
||||
id: userId,
|
||||
email: email,
|
||||
name: name,
|
||||
role: AppKeys.roleAdmin2,
|
||||
createdAt: now,
|
||||
lastSyncedAt: now,
|
||||
isActive: true,
|
||||
isSynced: true,
|
||||
sessionId: apiResult['session_id'],
|
||||
sessionExpiry: DateTime.parse(apiResult['session_expiry']),
|
||||
);
|
||||
|
||||
// Sauvegarder dans le repository local
|
||||
await saveUser(newAdmin);
|
||||
|
||||
// Configurer la session dans l'API
|
||||
setSessionId(newAdmin.sessionId);
|
||||
|
||||
notifyListeners();
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur d\'inscription: $e');
|
||||
return false;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Login complet
|
||||
Future<bool> login(String username, String password,
|
||||
{String type = 'admin'}) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
debugPrint('Début du processus de connexion pour: $username');
|
||||
|
||||
// Supprimer les références aux boîtes non définies dans AppKeys
|
||||
// pour éviter les erreurs de suppression de boîtes non référencées
|
||||
final nonDefinedBoxes = ['auth', 'locations', 'messages'];
|
||||
for (final boxName in nonDefinedBoxes) {
|
||||
try {
|
||||
if (Hive.isBoxOpen(boxName)) {
|
||||
debugPrint('Fermeture de la boîte non référencée: $boxName');
|
||||
await Hive.box(boxName).close();
|
||||
}
|
||||
|
||||
// Supprimer la boîte du disque
|
||||
await Hive.deleteBoxFromDisk(boxName);
|
||||
debugPrint('Nettoyage: Box $boxName supprimée');
|
||||
} catch (e) {
|
||||
debugPrint(
|
||||
'Erreur lors de la suppression de la boîte non référencée $boxName: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// S'assurer que toutes les Hive boxes sont vides avant de se connecter
|
||||
// Vider toutes les boîtes Hive SAUF la boîte des utilisateurs
|
||||
debugPrint('Nettoyage des données existantes avant connexion...');
|
||||
|
||||
// Sur le web, utiliser notre méthode sécurisée pour nettoyer les boîtes Hive
|
||||
if (kIsWeb) {
|
||||
await HiveWebFix.safeCleanHiveBoxes(
|
||||
excludeBoxes: [AppKeys.usersBoxName]);
|
||||
}
|
||||
// Sur iOS, nettoyer les fichiers Hive directement
|
||||
else if (!kIsWeb && Platform.isIOS) {
|
||||
await _cleanHiveFilesOnIOS();
|
||||
}
|
||||
// Sur Android, nettoyer les fichiers Hive directement
|
||||
else if (!kIsWeb && Platform.isAndroid) {
|
||||
await _cleanHiveFilesOnAndroid();
|
||||
}
|
||||
|
||||
// Nettoyer les boîtes sans les fermer
|
||||
await _clearAndRecreateBoxes();
|
||||
|
||||
// Initialiser les boîtes nécessaires avant d'appeler l'API
|
||||
// Cela garantit que toutes les boîtes sont ouvertes avant le traitement des données
|
||||
await _initializeBoxes();
|
||||
|
||||
// Appeler l'API
|
||||
debugPrint('Appel de l\'API de connexion (type: $type)...');
|
||||
final apiResult = await loginAPI(username, password, type: type);
|
||||
|
||||
// Vérifier le statut de la réponse
|
||||
final status = apiResult['status'] as String?;
|
||||
final message = apiResult['message'] as String?;
|
||||
|
||||
// Si le statut n'est pas 'success', retourner false
|
||||
if (status != 'success') {
|
||||
debugPrint('Échec de connexion: $message');
|
||||
return false;
|
||||
}
|
||||
|
||||
debugPrint('Connexion réussie, traitement des données...');
|
||||
|
||||
// [Reste de la méthode login inchangé...]
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur de connexion: $e');
|
||||
return false;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode pour vérifier si une boîte est ouverte et l'ouvrir si nécessaire
|
||||
Future<void> _ensureBoxIsOpen(String boxName) async {
|
||||
try {
|
||||
if (!Hive.isBoxOpen(boxName)) {
|
||||
debugPrint('Ouverture de la boîte $boxName...');
|
||||
if (boxName == AppKeys.passagesBoxName) {
|
||||
await Hive.openBox<PassageModel>(boxName);
|
||||
} else if (boxName == AppKeys.operationsBoxName) {
|
||||
await Hive.openBox<OperationModel>(boxName);
|
||||
} else if (boxName == AppKeys.sectorsBoxName) {
|
||||
await Hive.openBox<SectorModel>(boxName);
|
||||
} else if (boxName == AppKeys.usersBoxName) {
|
||||
await Hive.openBox<UserModel>(boxName);
|
||||
} else if (boxName == AppKeys.membresBoxName) {
|
||||
await Hive.openBox<MembreModel>(boxName);
|
||||
} else if (boxName == AppKeys.settingsBoxName) {
|
||||
await Hive.openBox(boxName);
|
||||
} else if (boxName == AppKeys.chatConversationsBoxName) {
|
||||
await Hive.openBox<ConversationModel>(boxName);
|
||||
} else if (boxName == AppKeys.chatMessagesBoxName) {
|
||||
await Hive.openBox<MessageModel>(boxName);
|
||||
} else {
|
||||
await Hive.openBox(boxName);
|
||||
}
|
||||
// Boîte ouverte avec succès
|
||||
} else {
|
||||
// La boîte est déjà ouverte
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de l\'ouverture de la boîte $boxName: $e');
|
||||
throw Exception('Impossible d\'ouvrir la boîte $boxName: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode pour vider et recréer toutes les boîtes Hive sauf la boîte des utilisateurs
|
||||
Future<void> _clearAndRecreateBoxes() async {
|
||||
try {
|
||||
debugPrint('Début de la suppression complète des données Hive...');
|
||||
|
||||
// Supprimer les références aux boîtes non définies dans AppKeys
|
||||
// pour éviter les erreurs de suppression de boîtes non référencées
|
||||
final nonDefinedBoxes = ['auth', 'locations', 'messages'];
|
||||
for (final boxName in nonDefinedBoxes) {
|
||||
try {
|
||||
if (Hive.isBoxOpen(boxName)) {
|
||||
debugPrint('Fermeture de la boîte non référencée: $boxName');
|
||||
await Hive.box(boxName).close();
|
||||
}
|
||||
|
||||
// Supprimer la boîte du disque
|
||||
await Hive.deleteBoxFromDisk(boxName);
|
||||
debugPrint('Nettoyage: Box $boxName supprimée');
|
||||
} catch (e) {
|
||||
debugPrint(
|
||||
'Erreur lors de la suppression de la boîte non référencée $boxName: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Sur le web, utiliser notre méthode sécurisée pour nettoyer les boîtes Hive
|
||||
if (kIsWeb) {
|
||||
await HiveWebFix.safeCleanHiveBoxes(
|
||||
excludeBoxes: [AppKeys.usersBoxName]);
|
||||
}
|
||||
// Sur iOS, nettoyer les fichiers Hive directement
|
||||
else if (Platform.isIOS) {
|
||||
await _cleanHiveFilesOnIOS();
|
||||
}
|
||||
// Sur Android, nettoyer les fichiers Hive directement
|
||||
else if (Platform.isAndroid) {
|
||||
await _cleanHiveFilesOnAndroid();
|
||||
}
|
||||
|
||||
// Liste des noms de boîtes à supprimer
|
||||
final boxesToDelete = [
|
||||
AppKeys.passagesBoxName,
|
||||
AppKeys.operationsBoxName,
|
||||
AppKeys.sectorsBoxName,
|
||||
AppKeys.chatConversationsBoxName,
|
||||
AppKeys.chatMessagesBoxName,
|
||||
];
|
||||
|
||||
// Vider chaque boîte sans la fermer
|
||||
for (final boxName in boxesToDelete) {
|
||||
try {
|
||||
debugPrint('Nettoyage de la boîte: $boxName');
|
||||
|
||||
// Vérifier si la boîte est déjà ouverte
|
||||
if (Hive.isBoxOpen(boxName)) {
|
||||
// Vider la boîte sans la fermer
|
||||
debugPrint('Boîte $boxName déjà ouverte, vidage sans fermeture');
|
||||
if (boxName == AppKeys.passagesBoxName) {
|
||||
await Hive.box<PassageModel>(boxName).clear();
|
||||
} else if (boxName == AppKeys.operationsBoxName) {
|
||||
await Hive.box<OperationModel>(boxName).clear();
|
||||
} else if (boxName == AppKeys.sectorsBoxName) {
|
||||
await Hive.box<SectorModel>(boxName).clear();
|
||||
} else if (boxName == AppKeys.chatConversationsBoxName) {
|
||||
await Hive.box<ConversationModel>(boxName).clear();
|
||||
} else if (boxName == AppKeys.chatMessagesBoxName) {
|
||||
await Hive.box<MessageModel>(boxName).clear();
|
||||
}
|
||||
} else {
|
||||
// Supprimer la boîte du disque si elle n'est pas ouverte
|
||||
debugPrint('Boîte $boxName non ouverte, suppression du disque');
|
||||
await Hive.deleteBoxFromDisk(boxName);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors du nettoyage de la boîte $boxName: $e');
|
||||
// Tenter de supprimer la boîte du disque en cas d'erreur
|
||||
try {
|
||||
await Hive.deleteBoxFromDisk(boxName);
|
||||
} catch (deleteError) {
|
||||
debugPrint(
|
||||
'Impossible de supprimer la boîte $boxName: $deleteError');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attendre un court instant pour s'assurer que les opérations de suppression sont terminées
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
|
||||
// Recréer les boîtes avec la méthode sécurisée
|
||||
debugPrint('Recréation des boîtes Hive...');
|
||||
|
||||
// Utiliser notre méthode pour s'assurer que les boîtes sont ouvertes
|
||||
try {
|
||||
// Passages
|
||||
await _ensureBoxIsOpen(AppKeys.passagesBoxName);
|
||||
|
||||
// Opérations
|
||||
await _ensureBoxIsOpen(AppKeys.operationsBoxName);
|
||||
|
||||
// Secteurs
|
||||
await _ensureBoxIsOpen(AppKeys.sectorsBoxName);
|
||||
|
||||
// Chat
|
||||
await _ensureBoxIsOpen(AppKeys.chatConversationsBoxName);
|
||||
await _ensureBoxIsOpen(AppKeys.chatMessagesBoxName);
|
||||
|
||||
// Vérifier l'intégrité des boîtes après recréation
|
||||
await _verifyHiveBoxesIntegrity();
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la recréation des boîtes Hive: $e');
|
||||
// Tentative de récupération sur erreur
|
||||
if (kIsWeb) {
|
||||
debugPrint('Tentative de récupération sur le web...');
|
||||
await HiveWebFix.resetHiveCompletely();
|
||||
|
||||
// Réessayer d'ouvrir les boîtes
|
||||
await _ensureBoxIsOpen(AppKeys.passagesBoxName);
|
||||
await _ensureBoxIsOpen(AppKeys.operationsBoxName);
|
||||
await _ensureBoxIsOpen(AppKeys.sectorsBoxName);
|
||||
await _ensureBoxIsOpen(AppKeys.chatConversationsBoxName);
|
||||
await _ensureBoxIsOpen(AppKeys.chatMessagesBoxName);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la réinitialisation des boîtes Hive: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode pour vérifier l'intégrité des boîtes Hive après recréation
|
||||
Future<void> _verifyHiveBoxesIntegrity() async {
|
||||
try {
|
||||
debugPrint('Vérification de l\'intégrité des boîtes Hive...');
|
||||
|
||||
// Liste des boîtes à vérifier avec leur type
|
||||
final boxesToCheck = [
|
||||
{'name': AppKeys.passagesBoxName, 'type': 'passage'},
|
||||
{'name': AppKeys.operationsBoxName, 'type': 'operation'},
|
||||
{'name': AppKeys.sectorsBoxName, 'type': 'sector'},
|
||||
{'name': AppKeys.chatConversationsBoxName, 'type': 'conversation'},
|
||||
{'name': AppKeys.chatMessagesBoxName, 'type': 'message'},
|
||||
];
|
||||
|
||||
// Vérifier chaque boîte
|
||||
for (final boxInfo in boxesToCheck) {
|
||||
final boxName = boxInfo['name'] as String;
|
||||
final boxType = boxInfo['type'] as String;
|
||||
|
||||
try {
|
||||
if (Hive.isBoxOpen(boxName)) {
|
||||
// Utiliser une approche spécifique au type pour éviter les erreurs de typage
|
||||
Box box;
|
||||
try {
|
||||
if (boxType == 'passage') {
|
||||
box = Hive.box<PassageModel>(boxName);
|
||||
} else if (boxType == 'operation') {
|
||||
box = Hive.box<OperationModel>(boxName);
|
||||
} else if (boxType == 'sector') {
|
||||
box = Hive.box<SectorModel>(boxName);
|
||||
} else if (boxType == 'conversation') {
|
||||
box = Hive.box<ConversationModel>(boxName);
|
||||
} else if (boxType == 'message') {
|
||||
box = Hive.box<MessageModel>(boxName);
|
||||
} else {
|
||||
box = Hive.box(boxName);
|
||||
}
|
||||
|
||||
final count = box.length;
|
||||
debugPrint('Boîte $boxName: $count éléments');
|
||||
|
||||
// Si la boîte contient des éléments, c'est anormal après recréation
|
||||
if (count > 0) {
|
||||
debugPrint(
|
||||
'ATTENTION: La boîte $boxName contient encore des données après recréation');
|
||||
// Essayer de vider la boîte une dernière fois
|
||||
await box.clear();
|
||||
debugPrint('Vidage forcé de la boîte $boxName effectué');
|
||||
}
|
||||
} catch (typeError) {
|
||||
debugPrint(
|
||||
'Erreur de typage lors de la vérification de $boxName: $typeError');
|
||||
|
||||
// Tentative alternative sans typage spécifique
|
||||
try {
|
||||
box = Hive.box(boxName);
|
||||
final count = box.length;
|
||||
debugPrint('Boîte $boxName (sans typage): $count éléments');
|
||||
|
||||
if (count > 0) {
|
||||
await box.clear();
|
||||
debugPrint(
|
||||
'Vidage forcé de la boîte $boxName (sans typage) effectué');
|
||||
}
|
||||
} catch (e2) {
|
||||
debugPrint(
|
||||
'Impossible de vérifier la boîte $boxName même sans typage: $e2');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debugPrint(
|
||||
'Boîte $boxName non ouverte, impossible de vérifier l\'intégrité');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la vérification de la boîte $boxName: $e');
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('Vérification d\'intégrité terminée');
|
||||
} catch (e) {
|
||||
debugPrint(
|
||||
'Erreur lors de la vérification d\'intégrité des boîtes Hive: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode spéciale pour nettoyer IndexedDB sur le web
|
||||
Future<void> _clearIndexedDB() async {
|
||||
if (kIsWeb) {
|
||||
try {
|
||||
debugPrint('Nettoyage complet d\'IndexedDB sur le web...');
|
||||
// Utiliser JavaScript pour nettoyer IndexedDB
|
||||
js.context.callMethod('eval', [
|
||||
'''
|
||||
var request = indexedDB.deleteDatabase("geosector_app");
|
||||
request.onsuccess = function() { console.log("IndexedDB nettoyé avec succès"); };
|
||||
request.onerror = function() { console.log("Erreur lors du nettoyage d\'IndexedDB"); };
|
||||
'''
|
||||
]);
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
debugPrint('Nettoyage d\'IndexedDB terminé');
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors du nettoyage d\'IndexedDB: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode spéciale pour nettoyer les fichiers Hive sur iOS
|
||||
Future<void> _cleanHiveFilesOnIOS() async {
|
||||
if (!kIsWeb && Platform.isIOS) {
|
||||
try {
|
||||
debugPrint('Nettoyage des fichiers Hive sur iOS...');
|
||||
final appDir = await getApplicationDocumentsDirectory();
|
||||
final hiveDir = Directory('${appDir.path}/hive');
|
||||
|
||||
if (await hiveDir.exists()) {
|
||||
debugPrint('Suppression du répertoire Hive: ${hiveDir.path}');
|
||||
// Exclure le dossier des utilisateurs pour conserver les informations de session
|
||||
final entries = await hiveDir.list().toList();
|
||||
for (var entry in entries) {
|
||||
final name = entry.path.split('/').last;
|
||||
// Ne pas supprimer la boîte des utilisateurs
|
||||
if (!name.contains(AppKeys.usersBoxName)) {
|
||||
debugPrint('Suppression de: ${entry.path}');
|
||||
if (entry is Directory) {
|
||||
await entry.delete(recursive: true);
|
||||
} else if (entry is File) {
|
||||
await entry.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
debugPrint('Nettoyage des fichiers Hive sur iOS terminé');
|
||||
} else {
|
||||
debugPrint('Répertoire Hive non trouvé');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors du nettoyage des fichiers Hive sur iOS: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode spéciale pour nettoyer les fichiers Hive sur Android
|
||||
Future<void> _cleanHiveFilesOnAndroid() async {
|
||||
if (!kIsWeb && Platform.isAndroid) {
|
||||
try {
|
||||
debugPrint('Nettoyage des fichiers Hive sur Android...');
|
||||
final appDir = await getApplicationDocumentsDirectory();
|
||||
final hiveDir = Directory('${appDir.path}');
|
||||
|
||||
if (await hiveDir.exists()) {
|
||||
debugPrint('Recherche des fichiers Hive dans: ${hiveDir.path}');
|
||||
// Sur Android, les fichiers Hive sont directement dans le répertoire de l'application
|
||||
final entries = await hiveDir.list().toList();
|
||||
int filesDeleted = 0;
|
||||
|
||||
for (var entry in entries) {
|
||||
final name = entry.path.split('/').last;
|
||||
// Ne supprimer que les fichiers Hive, mais pas la boîte des utilisateurs
|
||||
if (name.endsWith('.hive') &&
|
||||
!name.contains(AppKeys.usersBoxName)) {
|
||||
debugPrint('Suppression du fichier Hive: ${entry.path}');
|
||||
if (entry is File) {
|
||||
await entry.delete();
|
||||
filesDeleted++;
|
||||
|
||||
// Supprimer également les fichiers lock associés
|
||||
final lockFile = File('${entry.path}.lock');
|
||||
if (await lockFile.exists()) {
|
||||
await lockFile.delete();
|
||||
debugPrint('Suppression du fichier lock: ${lockFile.path}');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint(
|
||||
'Nettoyage des fichiers Hive sur Android terminé. $filesDeleted fichiers supprimés.');
|
||||
} else {
|
||||
debugPrint('Répertoire d\'application non trouvé');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint(
|
||||
'Erreur lors du nettoyage des fichiers Hive sur Android: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Logout complet
|
||||
Future<bool> logout() async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
debugPrint('Début du processus de déconnexion...');
|
||||
|
||||
// S'assurer que la boîte des utilisateurs est ouverte avant tout
|
||||
await _ensureBoxIsOpen(AppKeys.usersBoxName);
|
||||
|
||||
// Supprimer les références aux boîtes non définies dans AppKeys
|
||||
final nonDefinedBoxes = ['auth', 'locations', 'messages'];
|
||||
for (final boxName in nonDefinedBoxes) {
|
||||
try {
|
||||
if (Hive.isBoxOpen(boxName)) {
|
||||
debugPrint('Fermeture de la boîte non référencée: $boxName');
|
||||
await Hive.box(boxName).close();
|
||||
}
|
||||
|
||||
// Supprimer la boîte du disque
|
||||
await Hive.deleteBoxFromDisk(boxName);
|
||||
debugPrint('Nettoyage: Box $boxName supprimée');
|
||||
} catch (e) {
|
||||
debugPrint(
|
||||
'Erreur lors de la suppression de la boîte non référencée $boxName: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Récupérer l'utilisateur actuel avant de nettoyer les données
|
||||
final currentUser = getCurrentUser();
|
||||
if (currentUser == null) {
|
||||
debugPrint('Aucun utilisateur connecté, déconnexion terminée');
|
||||
return true;
|
||||
}
|
||||
|
||||
debugPrint('Déconnexion de l\'utilisateur: ${currentUser.email}');
|
||||
|
||||
// Appeler l'API pour déconnecter la session
|
||||
if (currentUser.sessionId != null) {
|
||||
debugPrint('Déconnexion de la session API...');
|
||||
await logoutAPI();
|
||||
}
|
||||
|
||||
// Effacer la session de l'utilisateur
|
||||
debugPrint('Mise à jour de l\'utilisateur pour effacer la session...');
|
||||
final updatedUser = currentUser.copyWith(
|
||||
sessionId: null,
|
||||
sessionExpiry: null,
|
||||
lastPath:
|
||||
null, // Réinitialiser le chemin pour revenir à l'écran de connexion
|
||||
);
|
||||
|
||||
// Sauvegarder l'utilisateur sans session
|
||||
await saveUser(updatedUser);
|
||||
|
||||
// Effacer la session de l'API
|
||||
setSessionId(null);
|
||||
|
||||
// Maintenant, nettoyer les données
|
||||
debugPrint('Nettoyage des données...');
|
||||
|
||||
// Sur le web, utiliser notre méthode sécurisée pour nettoyer les boîtes Hive
|
||||
if (kIsWeb) {
|
||||
await HiveWebFix.safeCleanHiveBoxes(
|
||||
excludeBoxes: [AppKeys.usersBoxName]);
|
||||
}
|
||||
// Sur iOS, nettoyer les fichiers Hive directement
|
||||
else if (!kIsWeb && Platform.isIOS) {
|
||||
await _cleanHiveFilesOnIOS();
|
||||
}
|
||||
// Sur Android, nettoyer les fichiers Hive directement
|
||||
else if (!kIsWeb && Platform.isAndroid) {
|
||||
await _cleanHiveFilesOnAndroid();
|
||||
}
|
||||
|
||||
// Vider les boîtes sans les fermer, y compris les boîtes de chat
|
||||
debugPrint('Suppression des données Hive...');
|
||||
await _clearAndRecreateBoxes();
|
||||
|
||||
// Vider spécifiquement les boîtes de chat si elles sont ouvertes
|
||||
try {
|
||||
if (Hive.isBoxOpen(AppKeys.chatConversationsBoxName)) {
|
||||
await Hive.box<ConversationModel>(AppKeys.chatConversationsBoxName).clear();
|
||||
debugPrint('Boîte conversations vidée');
|
||||
}
|
||||
if (Hive.isBoxOpen(AppKeys.chatMessagesBoxName)) {
|
||||
await Hive.box<MessageModel>(AppKeys.chatMessagesBoxName).clear();
|
||||
debugPrint('Boîte messages vidée');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors du vidage des boîtes de chat: $e');
|
||||
}
|
||||
|
||||
debugPrint('Déconnexion terminée avec succès');
|
||||
|
||||
notifyListeners();
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur de déconnexion: $e');
|
||||
return false;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Obtenir tous les utilisateurs locaux
|
||||
List<UserModel> getAllUsers() {
|
||||
return _userBox.values.toList();
|
||||
}
|
||||
|
||||
// Obtenir un utilisateur par son ID
|
||||
UserModel? getUserById(int id) {
|
||||
return _userBox.get(id);
|
||||
}
|
||||
|
||||
// Obtenir un utilisateur par son email
|
||||
UserModel? getUserByEmail(String email) {
|
||||
try {
|
||||
return _userBox.values.firstWhere(
|
||||
(user) => user.email == email,
|
||||
);
|
||||
} catch (e) {
|
||||
return null; // Utilisateur non trouvé
|
||||
}
|
||||
}
|
||||
|
||||
// Créer ou mettre à jour un utilisateur localement
|
||||
Future<UserModel> saveUser(UserModel user) async {
|
||||
await _userBox.put(user.id, user);
|
||||
notifyListeners(); // Notifier les changements pour mettre à jour l'UI
|
||||
return user;
|
||||
}
|
||||
|
||||
// Supprimer un utilisateur localement
|
||||
Future<void> deleteUser(String id) async {
|
||||
await _userBox.delete(id);
|
||||
}
|
||||
|
||||
// Créer un nouvel utilisateur localement et tenter de le synchroniser
|
||||
Future<UserModel> createUser({
|
||||
required String email,
|
||||
required String name,
|
||||
required int role,
|
||||
}) async {
|
||||
// Générer un ID numérique temporaire (timestamp)
|
||||
final int tempId = DateTime.now().millisecondsSinceEpoch;
|
||||
final now = DateTime.now();
|
||||
|
||||
final user = UserModel(
|
||||
id: tempId,
|
||||
email: email,
|
||||
name: name,
|
||||
role: role,
|
||||
createdAt: now,
|
||||
lastSyncedAt: now,
|
||||
isSynced: false,
|
||||
);
|
||||
|
||||
await _userBox.put(user.id, user);
|
||||
|
||||
// Tenter de synchroniser si possible
|
||||
await syncUser(user);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
// Synchroniser un utilisateur spécifique avec le serveur
|
||||
Future<UserModel> syncUser(UserModel user) async {
|
||||
try {
|
||||
final hasConnection = await _apiService.hasInternetConnection();
|
||||
|
||||
if (!hasConnection) {
|
||||
return user;
|
||||
}
|
||||
|
||||
UserModel syncedUser;
|
||||
|
||||
if (!user.isSynced) {
|
||||
// Si l'utilisateur n'est pas encore synchronisé, le créer sur le serveur
|
||||
syncedUser = await _apiService.createUser(user);
|
||||
} else {
|
||||
// Sinon, mettre à jour les informations
|
||||
syncedUser = await _apiService.updateUser(user);
|
||||
}
|
||||
|
||||
// Mettre à jour l'utilisateur local avec les informations du serveur
|
||||
final updatedUser = syncedUser.copyWith(
|
||||
isSynced: true,
|
||||
lastSyncedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
await _userBox.put(updatedUser.id, updatedUser);
|
||||
return updatedUser;
|
||||
} catch (e) {
|
||||
// En cas d'erreur, garder l'utilisateur local tel quel
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
// Synchroniser tous les utilisateurs non synchronisés
|
||||
Future<void> syncAllUsers() async {
|
||||
try {
|
||||
final hasConnection = await _apiService.hasInternetConnection();
|
||||
|
||||
if (!hasConnection) {
|
||||
return;
|
||||
}
|
||||
|
||||
final unsyncedUsers =
|
||||
_userBox.values.where((user) => !user.isSynced).toList();
|
||||
|
||||
if (unsyncedUsers.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Synchroniser en batch
|
||||
final result = await _apiService.syncData(users: unsyncedUsers);
|
||||
|
||||
// Mettre à jour les utilisateurs locaux
|
||||
if (result['users'] != null) {
|
||||
for (final userData in result['users']) {
|
||||
final syncedUser = UserModel.fromJson(userData);
|
||||
await _userBox.put(
|
||||
syncedUser.id,
|
||||
syncedUser.copyWith(
|
||||
isSynced: true,
|
||||
lastSyncedAt: DateTime.now(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Gérer les erreurs de synchronisation
|
||||
print('Erreur de synchronisation des utilisateurs: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Rafraîchir les données depuis le serveur
|
||||
Future<void> refreshFromServer() async {
|
||||
try {
|
||||
final hasConnection = await _apiService.hasInternetConnection();
|
||||
|
||||
if (!hasConnection) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Récupérer tous les utilisateurs du serveur
|
||||
final serverUsers = await _apiService.getUsers();
|
||||
|
||||
// Mettre à jour la base locale
|
||||
for (final serverUser in serverUsers) {
|
||||
final updatedUser = serverUser.copyWith(
|
||||
isSynced: true,
|
||||
lastSyncedAt: DateTime.now(),
|
||||
);
|
||||
await _userBox.put(updatedUser.id, updatedUser);
|
||||
}
|
||||
} catch (e) {
|
||||
// Gérer les erreurs
|
||||
print('Erreur lors du rafraîchissement des données: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Synchroniser les données utilisateur
|
||||
Future<void> syncUserData() async {
|
||||
if (_syncService != null && currentUser != null) {
|
||||
await _syncService!.syncUserData(currentUser!.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Récupérer la dernière opération active (avec isActive == true)
|
||||
OperationModel? getCurrentOperation() {
|
||||
try {
|
||||
// Récupérer toutes les opérations
|
||||
final operations = _operationBox.values.toList();
|
||||
|
||||
// Filtrer pour ne garder que les opérations actives
|
||||
final activeOperations = operations.where((op) => op.isActive).toList();
|
||||
|
||||
// Si aucune opération active n'est trouvée, retourner null
|
||||
if (activeOperations.isEmpty) {
|
||||
return operations.isNotEmpty ? operations.last : null;
|
||||
}
|
||||
|
||||
// Retourner la dernière opération active
|
||||
return activeOperations.last;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la récupération de l\'opération actuelle: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Récupérer tous les secteurs de l'utilisateur
|
||||
List<SectorModel> getUserSectors() {
|
||||
try {
|
||||
return _sectorBox.values.toList();
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la récupération des secteurs: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Récupérer un secteur par son ID
|
||||
SectorModel? getSectorById(int id) {
|
||||
try {
|
||||
return _sectorBox.get(id);
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la récupération du secteur: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
204
flutt/lib/core/services/api_service.dart
Normal file
204
flutt/lib/core/services/api_service.dart
Normal file
@@ -0,0 +1,204 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:geosector_app/core/data/models/user_model.dart';
|
||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||
import 'package:retry/retry.dart';
|
||||
|
||||
class ApiService {
|
||||
final Dio _dio = Dio();
|
||||
final String _baseUrl = AppKeys.baseApiUrl;
|
||||
String? _sessionId;
|
||||
|
||||
ApiService() {
|
||||
_dio.options.baseUrl = _baseUrl;
|
||||
_dio.options.connectTimeout = AppKeys.connectionTimeout;
|
||||
_dio.options.receiveTimeout = AppKeys.receiveTimeout;
|
||||
_dio.options.headers.addAll(AppKeys.defaultHeaders);
|
||||
|
||||
// Ajouter des intercepteurs pour l'authentification par session
|
||||
_dio.interceptors.add(InterceptorsWrapper(onRequest: (options, handler) {
|
||||
// Ajouter le session_id comme token Bearer aux en-têtes si disponible
|
||||
if (_sessionId != null) {
|
||||
options.headers[AppKeys.sessionHeader] = 'Bearer $_sessionId';
|
||||
}
|
||||
return handler.next(options);
|
||||
}, onError: (DioException error, handler) {
|
||||
// Gérer les erreurs d'authentification (401)
|
||||
if (error.response?.statusCode == 401) {
|
||||
// Session expirée ou invalide
|
||||
_sessionId = null;
|
||||
}
|
||||
return handler.next(error);
|
||||
}));
|
||||
}
|
||||
|
||||
// Définir l'ID de session
|
||||
void setSessionId(String? sessionId) {
|
||||
_sessionId = sessionId;
|
||||
}
|
||||
|
||||
// Vérifier la connectivité réseau
|
||||
Future<bool> hasInternetConnection() async {
|
||||
final connectivityResult = await (Connectivity().checkConnectivity());
|
||||
return connectivityResult != ConnectivityResult.none;
|
||||
}
|
||||
|
||||
// Méthode POST générique
|
||||
Future<Response> post(String path, {dynamic data}) async {
|
||||
try {
|
||||
return await _dio.post(path, data: data);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode GET générique
|
||||
Future<Response> get(String path, {Map<String, dynamic>? queryParameters}) async {
|
||||
try {
|
||||
return await _dio.get(path, queryParameters: queryParameters);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode PUT générique
|
||||
Future<Response> put(String path, {dynamic data}) async {
|
||||
try {
|
||||
return await _dio.put(path, data: data);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode DELETE générique
|
||||
Future<Response> delete(String path) async {
|
||||
try {
|
||||
return await _dio.delete(path);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// Authentification avec PHP session
|
||||
Future<Map<String, dynamic>> login(String username, String password, {String type = 'admin'}) async {
|
||||
try {
|
||||
final response = await _dio.post(AppKeys.loginEndpoint, data: {
|
||||
'username': username,
|
||||
'password': password,
|
||||
'type': type, // Ajouter le type de connexion (user ou admin)
|
||||
});
|
||||
|
||||
// Vérifier la structure de la réponse
|
||||
final data = response.data as Map<String, dynamic>;
|
||||
final status = data['status'] as String?;
|
||||
|
||||
// Afficher le message en cas d'erreur
|
||||
if (status != 'success') {
|
||||
final message = data['message'] as String?;
|
||||
debugPrint('Erreur d\'authentification: $message');
|
||||
}
|
||||
|
||||
// Si le statut est 'success', récupérer le session_id
|
||||
if (status == 'success' && data.containsKey('session_id')) {
|
||||
final sessionId = data['session_id'];
|
||||
// Définir la session pour les futures requêtes
|
||||
if (sessionId != null) {
|
||||
setSessionId(sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// Déconnexion
|
||||
Future<void> logout() async {
|
||||
try {
|
||||
if (_sessionId != null) {
|
||||
await _dio.post(AppKeys.logoutEndpoint);
|
||||
_sessionId = null;
|
||||
}
|
||||
} catch (e) {
|
||||
// Même en cas d'erreur, on réinitialise la session
|
||||
_sessionId = null;
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// Utilisateurs
|
||||
Future<List<UserModel>> getUsers() async {
|
||||
try {
|
||||
final response = await retry(
|
||||
() => _dio.get('/users'),
|
||||
retryIf: (e) => e is SocketException || e is TimeoutException,
|
||||
);
|
||||
|
||||
return (response.data as List)
|
||||
.map((json) => UserModel.fromJson(json))
|
||||
.toList();
|
||||
} catch (e) {
|
||||
// Gérer les erreurs
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<UserModel> getUserById(int id) async {
|
||||
try {
|
||||
final response = await _dio.get('/users/$id');
|
||||
return UserModel.fromJson(response.data);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<UserModel> createUser(UserModel user) async {
|
||||
try {
|
||||
final response = await _dio.post('/users', data: user.toJson());
|
||||
return UserModel.fromJson(response.data);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<UserModel> updateUser(UserModel user) async {
|
||||
try {
|
||||
final response = await _dio.put('/users/${user.id}', data: user.toJson());
|
||||
return UserModel.fromJson(response.data);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteUser(String id) async {
|
||||
try {
|
||||
await _dio.delete('/users/$id');
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// Espace réservé pour les futures méthodes de gestion des profils
|
||||
|
||||
// Espace réservé pour les futures méthodes de gestion des données
|
||||
|
||||
// Synchronisation en batch
|
||||
Future<Map<String, dynamic>> syncData({
|
||||
List<UserModel>? users,
|
||||
}) async {
|
||||
try {
|
||||
final Map<String, dynamic> payload = {
|
||||
if (users != null) 'users': users.map((u) => u.toJson()).toList(),
|
||||
};
|
||||
|
||||
final response = await _dio.post('/sync', data: payload);
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
41
flutt/lib/core/services/auth_service.dart
Normal file
41
flutt/lib/core/services/auth_service.dart
Normal file
@@ -0,0 +1,41 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geosector_app/core/repositories/user_repository.dart';
|
||||
import 'package:geosector_app/presentation/widgets/loading_overlay.dart';
|
||||
|
||||
/// Service qui gère les opérations d'authentification avec affichage d'un overlay de chargement
|
||||
class AuthService {
|
||||
final UserRepository _userRepository;
|
||||
|
||||
AuthService(this._userRepository);
|
||||
|
||||
/// Méthode de connexion avec affichage d'un overlay de chargement
|
||||
Future<bool> login(BuildContext context, String username, String password,
|
||||
{String type = 'admin'}) async {
|
||||
return await LoadingOverlay.show(
|
||||
context: context,
|
||||
spinnerSize: 80.0, // Spinner plus grand
|
||||
strokeWidth: 6.0, // Trait plus épais
|
||||
future: _userRepository.login(username, password, type: type),
|
||||
);
|
||||
}
|
||||
|
||||
/// Méthode de déconnexion avec affichage d'un overlay de chargement
|
||||
Future<bool> logout(BuildContext context) async {
|
||||
return await LoadingOverlay.show(
|
||||
context: context,
|
||||
spinnerSize: 80.0, // Spinner plus grand
|
||||
strokeWidth: 6.0, // Trait plus épais
|
||||
future: _userRepository.logout(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Vérifie si un utilisateur est connecté
|
||||
bool isLoggedIn() {
|
||||
return _userRepository.isLoggedIn;
|
||||
}
|
||||
|
||||
/// Vérifie si l'utilisateur connecté est un administrateur
|
||||
bool isAdmin() {
|
||||
return _userRepository.isAdmin();
|
||||
}
|
||||
}
|
||||
157
flutt/lib/core/services/connectivity_service.dart
Normal file
157
flutt/lib/core/services/connectivity_service.dart
Normal file
@@ -0,0 +1,157 @@
|
||||
import 'dart:async';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
|
||||
/// Service qui gère la surveillance de l'état de connectivité de l'appareil
|
||||
class ConnectivityService extends ChangeNotifier {
|
||||
final Connectivity _connectivity = Connectivity();
|
||||
late StreamSubscription<List<ConnectivityResult>> _connectivitySubscription;
|
||||
|
||||
List<ConnectivityResult> _connectionStatus = [ConnectivityResult.none];
|
||||
bool _isInitialized = false;
|
||||
|
||||
/// Indique si l'appareil est connecté à Internet
|
||||
bool get isConnected {
|
||||
// Vérifie si la liste contient au moins un type de connexion autre que 'none'
|
||||
return _connectionStatus.any((result) => result != ConnectivityResult.none);
|
||||
}
|
||||
|
||||
/// Indique si l'appareil est connecté via WiFi
|
||||
bool get isWifi => _connectionStatus.contains(ConnectivityResult.wifi);
|
||||
|
||||
/// Indique si l'appareil est connecté via données mobiles (4G, 5G, etc.)
|
||||
bool get isMobile => _connectionStatus.contains(ConnectivityResult.mobile);
|
||||
|
||||
/// Retourne le type de connexion actuel (WiFi, données mobiles, etc.)
|
||||
List<ConnectivityResult> get connectionStatus => _connectionStatus;
|
||||
|
||||
/// Retourne le premier type de connexion actif (pour compatibilité avec l'ancien code)
|
||||
ConnectivityResult get primaryConnectionStatus {
|
||||
// Retourne le premier type de connexion qui n'est pas 'none', ou 'none' si tous sont 'none'
|
||||
return _connectionStatus.firstWhere(
|
||||
(result) => result != ConnectivityResult.none,
|
||||
orElse: () => ConnectivityResult.none
|
||||
);
|
||||
}
|
||||
|
||||
/// Obtient une description textuelle du type de connexion
|
||||
String get connectionType {
|
||||
// Si aucune connexion n'est disponible
|
||||
if (!isConnected) {
|
||||
return 'Aucune connexion';
|
||||
}
|
||||
|
||||
// Utiliser le premier type de connexion actif
|
||||
ConnectivityResult primaryStatus = primaryConnectionStatus;
|
||||
|
||||
switch (primaryStatus) {
|
||||
case ConnectivityResult.wifi:
|
||||
return 'WiFi';
|
||||
case ConnectivityResult.mobile:
|
||||
return 'Données mobiles';
|
||||
case ConnectivityResult.ethernet:
|
||||
return 'Ethernet';
|
||||
case ConnectivityResult.bluetooth:
|
||||
return 'Bluetooth';
|
||||
case ConnectivityResult.vpn:
|
||||
return 'VPN';
|
||||
case ConnectivityResult.none:
|
||||
return 'Aucune connexion';
|
||||
default:
|
||||
return 'Inconnu';
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructeur du service de connectivité
|
||||
ConnectivityService() {
|
||||
_initConnectivity();
|
||||
}
|
||||
|
||||
/// Initialise le service et commence à écouter les changements de connectivité
|
||||
Future<void> _initConnectivity() async {
|
||||
if (_isInitialized) return;
|
||||
|
||||
try {
|
||||
// En version web, on considère par défaut que la connexion est disponible
|
||||
// car la vérification de connectivité est moins fiable sur le web
|
||||
if (kIsWeb) {
|
||||
_connectionStatus = [ConnectivityResult.wifi]; // Valeur par défaut pour le web
|
||||
} else {
|
||||
_connectionStatus = await _connectivity.checkConnectivity();
|
||||
}
|
||||
|
||||
// S'abonner aux changements de connectivité
|
||||
_connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
|
||||
_isInitialized = true;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de l\'initialisation du service de connectivité: $e');
|
||||
|
||||
// En cas d'erreur en version web, on suppose que la connexion est disponible
|
||||
// car l'application web ne peut pas fonctionner sans connexion de toute façon
|
||||
if (kIsWeb) {
|
||||
_connectionStatus = [ConnectivityResult.wifi];
|
||||
_isInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Met à jour l'état de la connexion lorsqu'il change
|
||||
void _updateConnectionStatus(List<ConnectivityResult> results) {
|
||||
// Vérifier si la liste des résultats a changé
|
||||
bool hasChanged = false;
|
||||
|
||||
// Si les listes ont des longueurs différentes, elles sont différentes
|
||||
if (_connectionStatus.length != results.length) {
|
||||
hasChanged = true;
|
||||
} else {
|
||||
// Vérifier si les éléments sont différents
|
||||
for (int i = 0; i < _connectionStatus.length; i++) {
|
||||
if (i >= results.length || _connectionStatus[i] != results[i]) {
|
||||
hasChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasChanged) {
|
||||
_connectionStatus = results;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie manuellement l'état actuel de la connexion
|
||||
Future<List<ConnectivityResult>> checkConnectivity() async {
|
||||
try {
|
||||
// En version web, on considère par défaut que la connexion est disponible
|
||||
if (kIsWeb) {
|
||||
// En version web, on peut tenter de faire une requête réseau légère pour vérifier la connectivité
|
||||
// mais pour l'instant, on suppose que la connexion est disponible
|
||||
final results = [ConnectivityResult.wifi];
|
||||
_updateConnectionStatus(results);
|
||||
return results;
|
||||
} else {
|
||||
// Version mobile - utiliser l'API standard
|
||||
final results = await _connectivity.checkConnectivity();
|
||||
_updateConnectionStatus(results);
|
||||
return results;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la vérification de la connectivité: $e');
|
||||
// En cas d'erreur, on conserve l'état actuel
|
||||
return _connectionStatus;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
try {
|
||||
_connectivitySubscription.cancel();
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de l\'annulation de l\'abonnement de connectivité: $e');
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
182
flutt/lib/core/services/hive_web_fix.dart
Normal file
182
flutt/lib/core/services/hive_web_fix.dart
Normal file
@@ -0,0 +1,182 @@
|
||||
import 'dart:async';
|
||||
import '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';
|
||||
|
||||
/// Service pour gérer les problèmes spécifiques à Hive en version web
|
||||
class HiveWebFix {
|
||||
/// Nettoie en toute sécurité les boîtes Hive en version web
|
||||
/// Cette méthode est plus sûre que de supprimer directement IndexedDB
|
||||
static Future<void> safeCleanHiveBoxes({List<String>? excludeBoxes}) async {
|
||||
if (!kIsWeb) return;
|
||||
|
||||
try {
|
||||
debugPrint(
|
||||
'HiveWebFix: Nettoyage sécurisé des boîtes Hive en version web');
|
||||
|
||||
// Liste des boîtes à nettoyer
|
||||
final boxesToClean = [
|
||||
AppKeys.operationsBoxName,
|
||||
AppKeys.sectorsBoxName,
|
||||
AppKeys.passagesBoxName,
|
||||
];
|
||||
|
||||
// Exclure certaines boîtes si spécifié
|
||||
if (excludeBoxes != null) {
|
||||
boxesToClean.removeWhere((box) => excludeBoxes.contains(box));
|
||||
}
|
||||
|
||||
// Nettoyer chaque boîte individuellement au lieu de supprimer IndexedDB
|
||||
for (final boxName in boxesToClean) {
|
||||
try {
|
||||
if (Hive.isBoxOpen(boxName)) {
|
||||
debugPrint('HiveWebFix: Nettoyage de la boîte $boxName');
|
||||
final box = Hive.box(boxName);
|
||||
await box.clear();
|
||||
debugPrint('HiveWebFix: Boîte $boxName nettoyée avec succès');
|
||||
} else {
|
||||
debugPrint(
|
||||
'HiveWebFix: La boîte $boxName n\'est pas ouverte, ouverture temporaire');
|
||||
final box = await Hive.openBox(boxName);
|
||||
await box.clear();
|
||||
await box.close();
|
||||
debugPrint('HiveWebFix: Boîte $boxName nettoyée et fermée');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint(
|
||||
'HiveWebFix: Erreur lors du nettoyage de la boîte $boxName: $e');
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('HiveWebFix: Nettoyage sécurisé terminé');
|
||||
} catch (e) {
|
||||
debugPrint('HiveWebFix: Erreur lors du nettoyage sécurisé: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie l'intégrité des boîtes Hive et tente de les réparer si nécessaire
|
||||
static Future<bool> checkAndRepairHiveBoxes() async {
|
||||
if (!kIsWeb) return true;
|
||||
|
||||
try {
|
||||
debugPrint('HiveWebFix: Vérification de l\'intégrité des boîtes Hive');
|
||||
|
||||
// Vérifier si IndexedDB est accessible
|
||||
final isIndexedDBAvailable = js.context.hasProperty('indexedDB');
|
||||
if (!isIndexedDBAvailable) {
|
||||
debugPrint(
|
||||
'HiveWebFix: IndexedDB n\'est pas disponible dans ce navigateur');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Liste des boîtes essentielles
|
||||
final essentialBoxes = [
|
||||
AppKeys.usersBoxName,
|
||||
AppKeys.settingsBoxName,
|
||||
];
|
||||
|
||||
// Vérifier chaque boîte essentielle
|
||||
for (final boxName in essentialBoxes) {
|
||||
try {
|
||||
if (!Hive.isBoxOpen(boxName)) {
|
||||
debugPrint(
|
||||
'HiveWebFix: Ouverture de la boîte essentielle $boxName');
|
||||
await Hive.openBox(boxName);
|
||||
}
|
||||
|
||||
// Vérifier si la boîte est accessible
|
||||
final box = Hive.box(boxName);
|
||||
// Tenter une opération simple pour vérifier l'intégrité
|
||||
final length = box.length;
|
||||
debugPrint(
|
||||
'HiveWebFix: Boîte $boxName accessible avec $length éléments');
|
||||
} catch (e) {
|
||||
debugPrint('HiveWebFix: Erreur d\'accès à la boîte $boxName: $e');
|
||||
|
||||
// Tenter de réparer en réinitialisant Hive
|
||||
try {
|
||||
debugPrint(
|
||||
'HiveWebFix: Tentative de réparation de la boîte $boxName');
|
||||
// Fermer la boîte si elle est ouverte
|
||||
if (Hive.isBoxOpen(boxName)) {
|
||||
await Hive.box(boxName).close();
|
||||
}
|
||||
|
||||
// Réouvrir la boîte
|
||||
await Hive.openBox(boxName);
|
||||
debugPrint('HiveWebFix: Boîte $boxName réparée avec succès');
|
||||
} catch (repairError) {
|
||||
debugPrint(
|
||||
'HiveWebFix: Échec de la réparation de la boîte $boxName: $repairError');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('HiveWebFix: Toutes les boîtes essentielles sont intègres');
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('HiveWebFix: Erreur lors de la vérification d\'intégrité: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Réinitialise complètement Hive en cas de problème grave
|
||||
/// À utiliser en dernier recours car cela supprimera toutes les données
|
||||
static Future<void> resetHiveCompletely() async {
|
||||
if (!kIsWeb) return;
|
||||
|
||||
try {
|
||||
debugPrint('HiveWebFix: Réinitialisation complète de Hive');
|
||||
|
||||
// Fermer toutes les boîtes ouvertes
|
||||
final boxesToClose = [
|
||||
AppKeys.usersBoxName,
|
||||
AppKeys.operationsBoxName,
|
||||
AppKeys.sectorsBoxName,
|
||||
AppKeys.passagesBoxName,
|
||||
AppKeys.settingsBoxName,
|
||||
];
|
||||
|
||||
for (final boxName in boxesToClose) {
|
||||
if (Hive.isBoxOpen(boxName)) {
|
||||
debugPrint('HiveWebFix: Fermeture de la boîte $boxName');
|
||||
await Hive.box(boxName).close();
|
||||
}
|
||||
}
|
||||
|
||||
// Supprimer IndexedDB avec une approche plus sûre
|
||||
js.context.callMethod('eval', [
|
||||
'''
|
||||
(function() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var request = indexedDB.deleteDatabase("geosector_app");
|
||||
request.onsuccess = function() {
|
||||
console.log("IndexedDB nettoyé avec succès");
|
||||
resolve(true);
|
||||
};
|
||||
request.onerror = function(event) {
|
||||
console.log("Erreur lors du nettoyage d'IndexedDB", event);
|
||||
reject(event);
|
||||
};
|
||||
});
|
||||
})();
|
||||
'''
|
||||
]);
|
||||
|
||||
// Attendre un peu pour s'assurer que la suppression est terminée
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
|
||||
// Réinitialiser Hive
|
||||
await Hive.initFlutter();
|
||||
|
||||
// Réenregistrer les adaptateurs
|
||||
// Note: Cette partie devrait être gérée par le code principal de l'application
|
||||
|
||||
debugPrint('HiveWebFix: Réinitialisation complète terminée');
|
||||
} catch (e) {
|
||||
debugPrint('HiveWebFix: Erreur lors de la réinitialisation complète: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
164
flutt/lib/core/services/location_service.dart
Normal file
164
flutt/lib/core/services/location_service.dart
Normal file
@@ -0,0 +1,164 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
/// Service de géolocalisation pour gérer les permissions et l'accès à la position
|
||||
class LocationService {
|
||||
/// Vérifie si les services de localisation sont activés
|
||||
static Future<bool> isLocationServiceEnabled() async {
|
||||
// En version web, on considère que les services de localisation sont toujours activés
|
||||
// car la vérification est gérée différemment par le navigateur
|
||||
if (kIsWeb) {
|
||||
return true;
|
||||
}
|
||||
return await Geolocator.isLocationServiceEnabled();
|
||||
}
|
||||
|
||||
/// Vérifie et demande les permissions de localisation
|
||||
/// Retourne true si l'autorisation est accordée, false sinon
|
||||
static Future<bool> checkAndRequestPermission() async {
|
||||
// En version web, on considère que les permissions sont toujours accordées
|
||||
// car la gestion des permissions est différente et gérée par le navigateur
|
||||
if (kIsWeb) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// Vérifier si les services de localisation sont activés
|
||||
bool serviceEnabled = await isLocationServiceEnabled();
|
||||
if (!serviceEnabled) {
|
||||
// Les services de localisation ne sont pas activés, on ne peut pas demander la permission
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vérifier le statut actuel de la permission
|
||||
LocationPermission permission = await Geolocator.checkPermission();
|
||||
|
||||
if (permission == LocationPermission.denied) {
|
||||
// Demander la permission
|
||||
permission = await Geolocator.requestPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
// La permission a été refusée
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (permission == LocationPermission.deniedForever) {
|
||||
// La permission a été refusée définitivement
|
||||
return false;
|
||||
}
|
||||
|
||||
// La permission est accordée (whileInUse ou always)
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la vérification des permissions de localisation: $e');
|
||||
// En cas d'erreur, on retourne false pour être sûr
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtient la position actuelle de l'utilisateur
|
||||
/// Retourne null si la position ne peut pas être obtenue
|
||||
static Future<LatLng?> getCurrentPosition() async {
|
||||
try {
|
||||
// En version web, la géolocalisation fonctionne différemment
|
||||
// et peut être bloquée par le navigateur si le site n'est pas en HTTPS
|
||||
if (kIsWeb) {
|
||||
try {
|
||||
Position position = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high,
|
||||
);
|
||||
return LatLng(position.latitude, position.longitude);
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de l\'obtention de la position en version web: $e');
|
||||
// En version web, en cas d'erreur, on peut retourner une position par défaut
|
||||
// ou null selon les besoins de l'application
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Version mobile
|
||||
// Vérifier si l'autorisation est accordée
|
||||
bool hasPermission = await checkAndRequestPermission();
|
||||
if (!hasPermission) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Obtenir la position actuelle
|
||||
Position position = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high,
|
||||
);
|
||||
|
||||
return LatLng(position.latitude, position.longitude);
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de l\'obtention de la position: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie si l'application peut accéder à la position de l'utilisateur
|
||||
/// Retourne un message d'erreur si l'accès n'est pas possible, null sinon
|
||||
static Future<String?> getLocationErrorMessage() async {
|
||||
// En version web, on considère qu'il n'y a pas d'erreur de localisation
|
||||
// car la gestion des permissions est gérée par le navigateur
|
||||
if (kIsWeb) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Vérifier si les services de localisation sont activés
|
||||
bool serviceEnabled = await isLocationServiceEnabled();
|
||||
if (!serviceEnabled) {
|
||||
return 'Les services de localisation sont désactivés. Veuillez les activer dans les paramètres de votre appareil.';
|
||||
}
|
||||
|
||||
// Vérifier le statut actuel de la permission
|
||||
LocationPermission permission = await Geolocator.checkPermission();
|
||||
|
||||
if (permission == LocationPermission.denied) {
|
||||
return 'L\'accès à la localisation a été refusé. Cette application ne peut pas fonctionner sans cette autorisation.';
|
||||
}
|
||||
|
||||
if (permission == LocationPermission.deniedForever) {
|
||||
return 'L\'accès à la localisation a été définitivement refusé. Veuillez l\'autoriser dans les paramètres de votre appareil.';
|
||||
}
|
||||
|
||||
return null; // Pas d'erreur
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la vérification des erreurs de localisation: $e');
|
||||
// En cas d'erreur, on retourne null pour ne pas bloquer l'application
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Ouvre les paramètres de l'application pour permettre à l'utilisateur de modifier les autorisations
|
||||
static Future<void> openAppSettings() async {
|
||||
// En version web, cette fonctionnalité n'est pas disponible
|
||||
if (kIsWeb) {
|
||||
debugPrint('Ouverture des paramètres de l\'application non disponible en version web');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await Geolocator.openAppSettings();
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de l\'ouverture des paramètres de l\'application: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Ouvre les paramètres de localisation de l'appareil
|
||||
static Future<void> openLocationSettings() async {
|
||||
// En version web, cette fonctionnalité n'est pas disponible
|
||||
if (kIsWeb) {
|
||||
debugPrint('Ouverture des paramètres de localisation non disponible en version web');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await Geolocator.openLocationSettings();
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de l\'ouverture des paramètres de localisation: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
194
flutt/lib/core/services/passage_data_service.dart
Normal file
194
flutt/lib/core/services/passage_data_service.dart
Normal file
@@ -0,0 +1,194 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||
import 'package:geosector_app/core/repositories/passage_repository.dart';
|
||||
import 'package:geosector_app/core/repositories/user_repository.dart';
|
||||
|
||||
/// Service pour charger et filtrer les données de passages
|
||||
class PassageDataService {
|
||||
final PassageRepository passageRepository;
|
||||
final UserRepository userRepository;
|
||||
|
||||
PassageDataService({
|
||||
required this.passageRepository,
|
||||
required this.userRepository,
|
||||
});
|
||||
|
||||
/// Charge les données de passage depuis Hive
|
||||
///
|
||||
/// [daysToShow] : Nombre de jours à afficher
|
||||
/// [excludePassageTypes] : Types de passages à exclure
|
||||
/// [userId] : ID de l'utilisateur pour filtrer les passages (null = utilisateur actuel)
|
||||
/// [showAllPassages] : Si vrai, n'applique aucun filtrage par utilisateur
|
||||
List<Map<String, dynamic>> loadPassageData({
|
||||
required int daysToShow,
|
||||
List<int> excludePassageTypes = const [2],
|
||||
int? userId,
|
||||
bool showAllPassages = false,
|
||||
}) {
|
||||
// Récupérer tous les passages
|
||||
final passages = passageRepository.getAllPassages();
|
||||
|
||||
// Filtrer les passages pour exclure ceux avec fkType dans la liste d'exclusion
|
||||
final filteredPassages = passages
|
||||
.where((p) => !excludePassageTypes.contains(p.fkType))
|
||||
.toList();
|
||||
|
||||
if (filteredPassages.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Déterminer si on filtre par utilisateur ou si on prend tous les passages
|
||||
final passagesToUse = showAllPassages
|
||||
? filteredPassages
|
||||
: _filterPassagesByUser(filteredPassages, userId);
|
||||
|
||||
if (passagesToUse.isEmpty) {
|
||||
debugPrint('Aucun passage trouvé après filtrage');
|
||||
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;
|
||||
debugPrint(
|
||||
'Date de référence pour le graphique: ${DateFormat('dd/MM/yyyy').format(referenceDate)}');
|
||||
|
||||
// Définir la date de début (N jours avant la date de référence)
|
||||
final startDate = referenceDate.subtract(Duration(days: daysToShow - 1));
|
||||
debugPrint(
|
||||
'Date de début pour le graphique: ${DateFormat('dd/MM/yyyy').format(startDate)}');
|
||||
debugPrint(
|
||||
'Plage de dates du graphique: ${DateFormat('dd/MM/yyyy').format(startDate)} - ${DateFormat('dd/MM/yyyy').format(referenceDate)}');
|
||||
|
||||
// Regrouper les passages par date et type
|
||||
final Map<String, Map<int, int>> passagesByDateAndType = {};
|
||||
|
||||
// Initialiser le dictionnaire avec les N derniers jours
|
||||
for (int i = daysToShow - 1; i >= 0; i--) {
|
||||
final date = referenceDate.subtract(Duration(days: i));
|
||||
final dateStr =
|
||||
'${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
||||
passagesByDateAndType[dateStr] = {};
|
||||
}
|
||||
|
||||
// Ajouter tous les types de passage possibles pour chaque date
|
||||
for (final dateStr in passagesByDateAndType.keys) {
|
||||
for (final typeId in AppKeys.typesPassages.keys) {
|
||||
// Exclure les types dans la liste d'exclusion
|
||||
if (!excludePassageTypes.contains(typeId)) {
|
||||
passagesByDateAndType[dateStr]![typeId] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parcourir les passages et les regrouper par date et type
|
||||
for (final passage in passagesToUse) {
|
||||
if (passage.passedAt
|
||||
.isAfter(startDate.subtract(const Duration(days: 1))) &&
|
||||
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')}';
|
||||
final typeId = passage.fkType;
|
||||
|
||||
// Vérifier que le type n'est pas exclu
|
||||
if (!excludePassageTypes.contains(typeId)) {
|
||||
// Si la date existe dans notre dictionnaire, mettre à jour le compteur
|
||||
if (passagesByDateAndType.containsKey(dateStr)) {
|
||||
if (!passagesByDateAndType[dateStr]!.containsKey(typeId)) {
|
||||
passagesByDateAndType[dateStr]![typeId] = 0;
|
||||
}
|
||||
passagesByDateAndType[dateStr]![typeId] =
|
||||
(passagesByDateAndType[dateStr]![typeId] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convertir les données au format attendu par le graphique
|
||||
final List<Map<String, dynamic>> result = [];
|
||||
passagesByDateAndType.forEach((dateStr, typesCounts) {
|
||||
typesCounts.forEach((typeId, count) {
|
||||
result.add({
|
||||
'date': dateStr,
|
||||
'type_passage': typeId,
|
||||
'nb': count,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Filtre les passages par utilisateur
|
||||
List<dynamic> _filterPassagesByUser(List<dynamic> passages, int? userId) {
|
||||
// Récupérer l'ID de l'utilisateur actuel si nécessaire
|
||||
final int? currentUserId = userId ?? userRepository.getCurrentUser()?.id;
|
||||
|
||||
// Filtrer les passages pour l'utilisateur actuel
|
||||
final userPassages = passages
|
||||
.where((p) => currentUserId == null || p.fkUser == currentUserId)
|
||||
.toList();
|
||||
|
||||
if (userPassages.isEmpty) {
|
||||
debugPrint('Aucun passage trouvé pour l\'utilisateur $currentUserId');
|
||||
}
|
||||
|
||||
return userPassages;
|
||||
}
|
||||
|
||||
/// Charge et prépare les données pour le graphique en camembert
|
||||
///
|
||||
/// [excludePassageTypes] : Types de passages à exclure
|
||||
/// [userId] : ID de l'utilisateur pour filtrer les passages (null = utilisateur actuel)
|
||||
/// [showAllPassages] : Si vrai, n'applique aucun filtrage par utilisateur
|
||||
Map<int, int> loadPassageDataForPieChart({
|
||||
List<int> excludePassageTypes = const [2],
|
||||
int? userId,
|
||||
bool showAllPassages = false,
|
||||
}) {
|
||||
// Récupérer tous les passages
|
||||
final passages = passageRepository.getAllPassages();
|
||||
|
||||
// Filtrer les passages pour exclure ceux avec fkType dans la liste d'exclusion
|
||||
final filteredPassages = passages
|
||||
.where((p) => !excludePassageTypes.contains(p.fkType))
|
||||
.toList();
|
||||
|
||||
if (filteredPassages.isEmpty) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Déterminer si on filtre par utilisateur ou si on prend tous les passages
|
||||
final passagesToUse = showAllPassages
|
||||
? filteredPassages
|
||||
: _filterPassagesByUser(filteredPassages, userId);
|
||||
|
||||
if (passagesToUse.isEmpty) {
|
||||
debugPrint('Aucun passage trouvé après filtrage');
|
||||
return {};
|
||||
}
|
||||
|
||||
// Compter les passages par type
|
||||
final Map<int, int> passagesByType = {};
|
||||
|
||||
// Initialiser les compteurs pour tous les types de passage
|
||||
for (final typeId in AppKeys.typesPassages.keys) {
|
||||
// Exclure les types dans la liste d'exclusion
|
||||
if (!excludePassageTypes.contains(typeId)) {
|
||||
passagesByType[typeId] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Compter les passages par type
|
||||
for (final passage in passagesToUse) {
|
||||
final typeId = passage.fkType;
|
||||
if (!excludePassageTypes.contains(typeId)) {
|
||||
passagesByType[typeId] = (passagesByType[typeId] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return passagesByType;
|
||||
}
|
||||
}
|
||||
96
flutt/lib/core/services/sync_service.dart
Normal file
96
flutt/lib/core/services/sync_service.dart
Normal file
@@ -0,0 +1,96 @@
|
||||
import 'dart:async';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:geosector_app/core/repositories/user_repository.dart';
|
||||
|
||||
class SyncService {
|
||||
final UserRepository _userRepository;
|
||||
|
||||
StreamSubscription? _connectivitySubscription;
|
||||
Timer? _periodicSyncTimer;
|
||||
|
||||
bool _isSyncing = false;
|
||||
final Duration _syncInterval = const Duration(minutes: 15);
|
||||
|
||||
SyncService({
|
||||
required UserRepository userRepository,
|
||||
}) : _userRepository = userRepository {
|
||||
_initConnectivityListener();
|
||||
_initPeriodicSync();
|
||||
}
|
||||
|
||||
// Initialiser l'écouteur de connectivité
|
||||
void _initConnectivityListener() {
|
||||
_connectivitySubscription = Connectivity()
|
||||
.onConnectivityChanged
|
||||
.listen((List<ConnectivityResult> results) {
|
||||
// Vérifier si au moins un type de connexion est disponible
|
||||
if (results.any((result) => result != ConnectivityResult.none)) {
|
||||
// Lorsque la connexion est rétablie, déclencher une synchronisation
|
||||
syncAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialiser la synchronisation périodique
|
||||
void _initPeriodicSync() {
|
||||
_periodicSyncTimer = Timer.periodic(_syncInterval, (timer) {
|
||||
syncAll();
|
||||
});
|
||||
}
|
||||
|
||||
// Synchroniser toutes les données
|
||||
Future<void> syncAll() async {
|
||||
if (_isSyncing) return;
|
||||
|
||||
_isSyncing = true;
|
||||
|
||||
try {
|
||||
// Synchroniser les utilisateurs
|
||||
await _userRepository.syncAllUsers();
|
||||
} catch (e) {
|
||||
// Gérer les erreurs de synchronisation
|
||||
print('Erreur lors de la synchronisation: $e');
|
||||
} finally {
|
||||
_isSyncing = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Synchroniser uniquement les données d'un utilisateur spécifique
|
||||
Future<void> syncUserData(int userId) async {
|
||||
try {
|
||||
// Cette méthode pourrait être étendue à l'avenir pour synchroniser d'autres données utilisateur
|
||||
await _userRepository.refreshFromServer();
|
||||
} catch (e) {
|
||||
print('Erreur lors de la synchronisation des données utilisateur: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Forcer le rafraîchissement depuis le serveur
|
||||
Future<void> forceRefresh() async {
|
||||
if (_isSyncing) return;
|
||||
|
||||
_isSyncing = true;
|
||||
|
||||
try {
|
||||
// Rafraîchir depuis le serveur
|
||||
await _userRepository.refreshFromServer();
|
||||
} catch (e) {
|
||||
print('Erreur lors du rafraîchissement forcé: $e');
|
||||
} finally {
|
||||
_isSyncing = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Obtenir l'état de synchronisation
|
||||
Map<String, dynamic> getSyncStatus() {
|
||||
return {
|
||||
'isSyncing': _isSyncing,
|
||||
};
|
||||
}
|
||||
|
||||
// Nettoyer les ressources
|
||||
void dispose() {
|
||||
_connectivitySubscription?.cancel();
|
||||
_periodicSyncTimer?.cancel();
|
||||
}
|
||||
}
|
||||
122
flutt/lib/core/theme/app_theme.dart
Normal file
122
flutt/lib/core/theme/app_theme.dart
Normal file
@@ -0,0 +1,122 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class AppTheme {
|
||||
// Nouvelles couleurs du thème
|
||||
static const Color primaryColor = Color(0xFF2E4057); // Bleu foncé/gris
|
||||
static const Color secondaryColor = Color(0xFF048BA8); // Bleu turquoise
|
||||
static const Color accentColor = Color(0xFFF18F01); // Orange
|
||||
static const Color backgroundLightColor = Color(0xFFF9FAFB);
|
||||
static const Color backgroundDarkColor = Color(0xFF111827);
|
||||
static const Color textLightColor = Color(0xFF1F2937);
|
||||
static const Color textDarkColor = Color(0xFFF9FAFB);
|
||||
|
||||
// Thème clair
|
||||
static ThemeData get lightTheme {
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.light,
|
||||
colorScheme: ColorScheme.light(
|
||||
primary: primaryColor,
|
||||
secondary: secondaryColor,
|
||||
tertiary: accentColor,
|
||||
background: backgroundLightColor,
|
||||
surface: Colors.white,
|
||||
onPrimary: Colors.white,
|
||||
onSecondary: Colors.white,
|
||||
onBackground: textLightColor,
|
||||
onSurface: textLightColor,
|
||||
),
|
||||
textTheme: GoogleFonts.poppinsTextTheme(ThemeData.light().textTheme),
|
||||
appBarTheme: const AppBarTheme(
|
||||
backgroundColor: primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[100],
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: primaryColor, width: 2),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
),
|
||||
cardTheme: CardTheme(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Thème sombre
|
||||
static ThemeData get darkTheme {
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
colorScheme: ColorScheme.dark(
|
||||
primary: primaryColor,
|
||||
secondary: secondaryColor,
|
||||
tertiary: accentColor,
|
||||
background: backgroundDarkColor,
|
||||
surface: const Color(0xFF1F2937),
|
||||
onPrimary: Colors.white,
|
||||
onSecondary: Colors.white,
|
||||
onBackground: textDarkColor,
|
||||
onSurface: textDarkColor,
|
||||
),
|
||||
textTheme: GoogleFonts.poppinsTextTheme(ThemeData.dark().textTheme),
|
||||
appBarTheme: const AppBarTheme(
|
||||
backgroundColor: Color(0xFF1F2937),
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: const Color(0xFF374151),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: primaryColor, width: 2),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
),
|
||||
cardTheme: CardTheme(
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
color: const Color(0xFF1F2937),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user