Restructuration majeure du projet: migration de flutt vers app, ajout de l'API et mise à jour du site web

This commit is contained in:
d6soft
2025-05-16 09:19:03 +02:00
parent b5aafc424b
commit 5c2620de30
391 changed files with 19780 additions and 7233 deletions

View File

@@ -0,0 +1,192 @@
/// 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 amicaleBoxName = 'amicale';
static const String clientsBoxName = 'clients';
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 userSectorBoxName = 'user_sector';
static const String chatConversationsBoxName = 'chat_conversations';
static const String chatMessagesBoxName = 'chat_messages';
static const String regionsBoxName = 'regions';
// 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 pour les différents environnements
static const String baseApiUrlDev = 'https://dapp.geosector.fr/api';
static const String baseApiUrlRec = 'https://rapp.geosector.fr/api';
static const String baseApiUrlProd = 'https://app.geosector.fr/api';
// Identifiants d'application pour les différents environnements
static const String appIdentifierDev = 'dapp.geosector.fr';
static const String appIdentifierRec = 'rapp.geosector.fr';
static const String appIdentifierProd = 'app.geosector.fr';
// 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 mapboxApiKeyDev =
'pk.eyJ1IjoicHZkNnNvZnQiLCJhIjoiY21hanVmNjN5MTM5djJtczdsMW92cjQ0ciJ9.pUCMuvWPB3cuBaPh4ywTAw';
static const String mapboxApiKeyRec =
'pk.eyJ1IjoicHZkNnNvZnQiLCJhIjoiY21hanVlZ3FiMGx0NDJpc2k4YnkxaWZ2dSJ9.OqGJtjlWRgB4fIjECCB8WA';
static const String mapboxApiKeyProd =
'pk.eyJ1IjoicHZkNnNvZnQiLCJhIjoiY204dTNhNmd0MGV1ZzJqc2pnNnB0NjYxdSJ9.TA5Mvliyn91Oi01F_2Yuxw';
// Méthode pour obtenir la clé API Mapbox en fonction de l'environnement actuel
static String getMapboxApiKey(String environment) {
// Utiliser l'environnement passé en paramètre pour déterminer quelle clé retourner
switch (environment) {
case 'DEV':
return mapboxApiKeyDev;
case 'REC':
return mapboxApiKeyRec;
case 'PROD':
default:
return mapboxApiKeyProd;
}
}
// Pour la compatibilité avec le code existant, on garde un getter qui utilise
// l'environnement actuel (à utiliser uniquement si l'ApiService n'est pas disponible)
static String get mapboxApiKey {
// Note: Cette implémentation est une solution de secours et devrait être évitée
// Il est préférable d'utiliser getMapboxApiKey(apiService.getCurrentEnvironment())
// Détection basique de l'environnement basée sur l'URL en mode web
if (kIsWeb) {
// Essayer d'accéder à l'URL actuelle (fonctionne uniquement en mode web)
try {
final String currentUrl = Uri.base.toString().toLowerCase();
if (currentUrl.contains('dapp.geosector.fr')) {
return mapboxApiKeyDev;
} else if (currentUrl.contains('rapp.geosector.fr')) {
return mapboxApiKeyRec;
}
} catch (e) {
// En cas d'erreur, utiliser la clé de production par défaut
print('Erreur lors de la détection de l\'environnement: $e');
}
}
// Par défaut, retourner la clé de production
return mapboxApiKeyProd;
}
// Headers
static const String sessionHeader = 'Authorization';
// En-têtes par défaut pour les requêtes API
// Note: Ces en-têtes seront complétés dynamiquement dans ApiService
static const Map<String, String> defaultHeaders = {
'Content-Type': 'application/json',
'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 (basés sur la maquette Figma)
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': 0xFFB87333, // Couleur cuivrée
'icon_data': Icons.payments_outlined,
},
2: {
'titre': 'Chèque',
'couleur': 0xFFD8D5EC, // Violet clair (Figma)
'icon_data': Icons.account_balance_wallet_outlined,
},
3: {
'titre': 'CB',
'couleur': 0xFF0099FF, // Bleu flashy
'icon_data': Icons.credit_card,
},
};
// Types de passages (basés sur la maquette Figma)
static const Map<int, Map<String, dynamic>> typesPassages = {
1: {
'titres': 'Effectués',
'titre': 'Effectué',
'couleur1': 0xFF00E09D, // Vert (Figma)
'couleur2': 0xFF00E09D, // Vert (Figma)
'couleur3': 0xFF00E09D, // Vert (Figma)
'icon_data': Icons.task_alt,
},
2: {
'titres': 'À finaliser',
'titre': 'À finaliser',
'couleur1': 0xFFFFFFFF, // Blanc
'couleur2': 0xFFF7A278, // Orange (Figma)
'couleur3': 0xFFE65100, // Orange foncé
'icon_data': Icons.refresh,
},
3: {
'titres': 'Refusés',
'titre': 'Refusé',
'couleur1': 0xFFE41B13, // Rouge (Figma)
'couleur2': 0xFFE41B13, // Rouge (Figma)
'couleur3': 0xFFE41B13, // Rouge (Figma)
'icon_data': Icons.block,
},
4: {
'titres': 'Dons',
'titre': 'Don',
'couleur1': 0xFF395AA7, // Bleu (Figma)
'couleur2': 0xFF395AA7, // Bleu (Figma)
'couleur3': 0xFF395AA7, // Bleu (Figma)
'icon_data': Icons.volunteer_activism,
},
5: {
'titres': 'Lots',
'titre': 'Lot',
'couleur1': 0xFF20335E, // Bleu foncé (Figma)
'couleur2': 0xFF20335E, // Bleu foncé (Figma)
'couleur3': 0xFF20335E, // Bleu foncé (Figma)
'icon_data': Icons.layers,
},
6: {
'titres': 'Maisons vides',
'titre': 'Maison vide',
'couleur1': 0xFFB8B8B8, // Gris (Figma)
'couleur2': 0xFFB8B8B8, // Gris (Figma)
'couleur3': 0xFFB8B8B8, // Gris (Figma)
'icon_data': Icons.home_outlined,
},
};
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,249 @@
import 'package:hive/hive.dart';
part 'amicale_model.g.dart';
@HiveType(typeId: 11)
class AmicaleModel extends HiveObject {
@HiveField(0)
final int id;
@HiveField(1)
final String name;
@HiveField(2)
final String adresse1;
@HiveField(3)
final String adresse2;
@HiveField(4)
final String codePostal;
@HiveField(5)
final String ville;
@HiveField(6)
final int? fkRegion;
@HiveField(7)
final String? libRegion;
@HiveField(8)
final int? fkType;
@HiveField(9)
final String phone;
@HiveField(10)
final String mobile;
@HiveField(11)
final String email;
@HiveField(12)
final String gpsLat;
@HiveField(13)
final String gpsLng;
@HiveField(14)
final String stripeId;
@HiveField(15)
final bool chkDemo;
@HiveField(16)
final bool chkCopieMailRecu;
@HiveField(17)
final bool chkAcceptSms;
@HiveField(18)
final bool chkActive;
@HiveField(19)
final bool chkStripe;
@HiveField(20)
final DateTime? createdAt;
@HiveField(21)
final DateTime? updatedAt;
AmicaleModel({
required this.id,
required this.name,
this.adresse1 = '',
this.adresse2 = '',
this.codePostal = '',
this.ville = '',
this.fkRegion,
this.libRegion,
this.fkType,
this.phone = '',
this.mobile = '',
this.email = '',
this.gpsLat = '',
this.gpsLng = '',
this.stripeId = '',
this.chkDemo = false,
this.chkCopieMailRecu = false,
this.chkAcceptSms = false,
this.chkActive = true,
this.chkStripe = false,
this.createdAt,
this.updatedAt,
});
// Factory pour convertir depuis JSON (API)
factory AmicaleModel.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 fk_region en int si présent
final dynamic rawFkRegion = json['fk_region'];
final int? fkRegion = rawFkRegion != null
? (rawFkRegion is String ? int.parse(rawFkRegion) : rawFkRegion as int)
: null;
// Convertir fk_type en int si présent
final dynamic rawFkType = json['fk_type'];
final int? fkType = rawFkType != null
? (rawFkType is String ? int.parse(rawFkType) : rawFkType as int)
: null;
// Convertir les booléens
final bool chkDemo = json['chk_demo'] == 1 || json['chk_demo'] == true;
final bool chkCopieMailRecu =
json['chk_copie_mail_recu'] == 1 || json['chk_copie_mail_recu'] == true;
final bool chkAcceptSms =
json['chk_accept_sms'] == 1 || json['chk_accept_sms'] == true;
final bool chkActive =
json['chk_active'] == 1 || json['chk_active'] == true;
final bool chkStripe =
json['chk_stripe'] == 1 || json['chk_stripe'] == true;
// Traiter les dates si présentes
DateTime? createdAt;
if (json['created_at'] != null && json['created_at'] != '') {
try {
createdAt = DateTime.parse(json['created_at']);
} catch (e) {
createdAt = null;
}
}
DateTime? updatedAt;
if (json['updated_at'] != null && json['updated_at'] != '') {
try {
updatedAt = DateTime.parse(json['updated_at']);
} catch (e) {
updatedAt = null;
}
}
return AmicaleModel(
id: id,
name: json['name'] ?? '',
adresse1: json['adresse1'] ?? '',
adresse2: json['adresse2'] ?? '',
codePostal: json['code_postal'] ?? '',
ville: json['ville'] ?? '',
fkRegion: fkRegion,
libRegion: json['lib_region'],
fkType: fkType,
phone: json['phone'] ?? '',
mobile: json['mobile'] ?? '',
email: json['email'] ?? '',
gpsLat: json['gps_lat'] ?? '',
gpsLng: json['gps_lng'] ?? '',
stripeId: json['stripe_id'] ?? '',
chkDemo: chkDemo,
chkCopieMailRecu: chkCopieMailRecu,
chkAcceptSms: chkAcceptSms,
chkActive: chkActive,
chkStripe: chkStripe,
createdAt: createdAt,
updatedAt: updatedAt,
);
}
// Convertir en JSON pour l'API
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'adresse1': adresse1,
'adresse2': adresse2,
'code_postal': codePostal,
'ville': ville,
'fk_region': fkRegion,
'lib_region': libRegion,
'fk_type': fkType,
'phone': phone,
'mobile': mobile,
'email': email,
'gps_lat': gpsLat,
'gps_lng': gpsLng,
'stripe_id': stripeId,
'chk_demo': chkDemo ? 1 : 0,
'chk_copie_mail_recu': chkCopieMailRecu ? 1 : 0,
'chk_accept_sms': chkAcceptSms ? 1 : 0,
'chk_active': chkActive ? 1 : 0,
'chk_stripe': chkStripe ? 1 : 0,
'created_at': createdAt?.toIso8601String(),
'updated_at': updatedAt?.toIso8601String(),
};
}
// Copier avec de nouvelles valeurs
AmicaleModel copyWith({
String? name,
String? adresse1,
String? adresse2,
String? codePostal,
String? ville,
int? fkRegion,
String? libRegion,
int? fkType,
String? phone,
String? mobile,
String? email,
String? gpsLat,
String? gpsLng,
String? stripeId,
bool? chkDemo,
bool? chkCopieMailRecu,
bool? chkAcceptSms,
bool? chkActive,
bool? chkStripe,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return AmicaleModel(
id: this.id,
name: name ?? this.name,
adresse1: adresse1 ?? this.adresse1,
adresse2: adresse2 ?? this.adresse2,
codePostal: codePostal ?? this.codePostal,
ville: ville ?? this.ville,
fkRegion: fkRegion ?? this.fkRegion,
libRegion: libRegion ?? this.libRegion,
fkType: fkType ?? this.fkType,
phone: phone ?? this.phone,
mobile: mobile ?? this.mobile,
email: email ?? this.email,
gpsLat: gpsLat ?? this.gpsLat,
gpsLng: gpsLng ?? this.gpsLng,
stripeId: stripeId ?? this.stripeId,
chkDemo: chkDemo ?? this.chkDemo,
chkCopieMailRecu: chkCopieMailRecu ?? this.chkCopieMailRecu,
chkAcceptSms: chkAcceptSms ?? this.chkAcceptSms,
chkActive: chkActive ?? this.chkActive,
chkStripe: chkStripe ?? this.chkStripe,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
}

View File

@@ -0,0 +1,104 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'amicale_model.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class AmicaleModelAdapter extends TypeAdapter<AmicaleModel> {
@override
final int typeId = 11;
@override
AmicaleModel read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return AmicaleModel(
id: fields[0] as int,
name: fields[1] as String,
adresse1: fields[2] as String,
adresse2: fields[3] as String,
codePostal: fields[4] as String,
ville: fields[5] as String,
fkRegion: fields[6] as int?,
libRegion: fields[7] as String?,
fkType: fields[8] as int?,
phone: fields[9] as String,
mobile: fields[10] as String,
email: fields[11] as String,
gpsLat: fields[12] as String,
gpsLng: fields[13] as String,
stripeId: fields[14] as String,
chkDemo: fields[15] as bool,
chkCopieMailRecu: fields[16] as bool,
chkAcceptSms: fields[17] as bool,
chkActive: fields[18] as bool,
chkStripe: fields[19] as bool,
createdAt: fields[20] as DateTime?,
updatedAt: fields[21] as DateTime?,
);
}
@override
void write(BinaryWriter writer, AmicaleModel obj) {
writer
..writeByte(22)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.name)
..writeByte(2)
..write(obj.adresse1)
..writeByte(3)
..write(obj.adresse2)
..writeByte(4)
..write(obj.codePostal)
..writeByte(5)
..write(obj.ville)
..writeByte(6)
..write(obj.fkRegion)
..writeByte(7)
..write(obj.libRegion)
..writeByte(8)
..write(obj.fkType)
..writeByte(9)
..write(obj.phone)
..writeByte(10)
..write(obj.mobile)
..writeByte(11)
..write(obj.email)
..writeByte(12)
..write(obj.gpsLat)
..writeByte(13)
..write(obj.gpsLng)
..writeByte(14)
..write(obj.stripeId)
..writeByte(15)
..write(obj.chkDemo)
..writeByte(16)
..write(obj.chkCopieMailRecu)
..writeByte(17)
..write(obj.chkAcceptSms)
..writeByte(18)
..write(obj.chkActive)
..writeByte(19)
..write(obj.chkStripe)
..writeByte(20)
..write(obj.createdAt)
..writeByte(21)
..write(obj.updatedAt);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is AmicaleModelAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@@ -0,0 +1,200 @@
import 'package:hive/hive.dart';
part 'client_model.g.dart';
@HiveType(typeId: 10)
class ClientModel extends HiveObject {
@HiveField(0)
final int id;
@HiveField(1)
final String name;
@HiveField(2)
final String? adresse1;
@HiveField(3)
final String? adresse2;
@HiveField(4)
final String? codePostal;
@HiveField(5)
final String? ville;
@HiveField(6)
final int? fkRegion;
@HiveField(7)
final String? libRegion;
@HiveField(8)
final int? fkType;
@HiveField(9)
final String? phone;
@HiveField(10)
final String? mobile;
@HiveField(11)
final String? email;
@HiveField(12)
final String? gpsLat;
@HiveField(13)
final String? gpsLng;
@HiveField(14)
final String? stripeId;
@HiveField(15)
final bool? chkDemo;
@HiveField(16)
final bool? chkCopieMailRecu;
@HiveField(17)
final bool? chkAcceptSms;
@HiveField(18)
final bool? chkActive;
ClientModel({
required this.id,
required this.name,
this.adresse1,
this.adresse2,
this.codePostal,
this.ville,
this.fkRegion,
this.libRegion,
this.fkType,
this.phone,
this.mobile,
this.email,
this.gpsLat,
this.gpsLng,
this.stripeId,
this.chkDemo,
this.chkCopieMailRecu,
this.chkAcceptSms,
this.chkActive,
});
// Factory pour convertir depuis JSON (API)
factory ClientModel.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 fk_region en int si présent
int? fkRegion;
if (json['fk_region'] != null) {
final dynamic rawFkRegion = json['fk_region'];
fkRegion =
rawFkRegion is String ? int.parse(rawFkRegion) : rawFkRegion as int;
}
// Convertir fk_type en int si présent
int? fkType;
if (json['fk_type'] != null) {
final dynamic rawFkType = json['fk_type'];
fkType = rawFkType is String ? int.parse(rawFkType) : rawFkType as int;
}
return ClientModel(
id: id,
name: json['name'] ?? '',
adresse1: json['adresse1'],
adresse2: json['adresse2'],
codePostal: json['code_postal'],
ville: json['ville'],
fkRegion: fkRegion,
libRegion: json['lib_region'],
fkType: fkType,
phone: json['phone'],
mobile: json['mobile'],
email: json['email'],
gpsLat: json['gps_lat'],
gpsLng: json['gps_lng'],
stripeId: json['stripe_id'],
chkDemo: json['chk_demo'] == 1 || json['chk_demo'] == true,
chkCopieMailRecu: json['chk_copie_mail_recu'] == 1 ||
json['chk_copie_mail_recu'] == true,
chkAcceptSms:
json['chk_accept_sms'] == 1 || json['chk_accept_sms'] == true,
chkActive: json['chk_active'] == 1 || json['chk_active'] == true,
);
}
// Convertir en JSON pour l'API
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'adresse1': adresse1,
'adresse2': adresse2,
'code_postal': codePostal,
'ville': ville,
'fk_region': fkRegion,
'lib_region': libRegion,
'fk_type': fkType,
'phone': phone,
'mobile': mobile,
'email': email,
'gps_lat': gpsLat,
'gps_lng': gpsLng,
'stripe_id': stripeId,
'chk_demo': chkDemo,
'chk_copie_mail_recu': chkCopieMailRecu,
'chk_accept_sms': chkAcceptSms,
'chk_active': chkActive,
};
}
// Copier avec de nouvelles valeurs
ClientModel copyWith({
String? name,
String? adresse1,
String? adresse2,
String? codePostal,
String? ville,
int? fkRegion,
String? libRegion,
int? fkType,
String? phone,
String? mobile,
String? email,
String? gpsLat,
String? gpsLng,
String? stripeId,
bool? chkDemo,
bool? chkCopieMailRecu,
bool? chkAcceptSms,
bool? chkActive,
}) {
return ClientModel(
id: this.id,
name: name ?? this.name,
adresse1: adresse1 ?? this.adresse1,
adresse2: adresse2 ?? this.adresse2,
codePostal: codePostal ?? this.codePostal,
ville: ville ?? this.ville,
fkRegion: fkRegion ?? this.fkRegion,
libRegion: libRegion ?? this.libRegion,
fkType: fkType ?? this.fkType,
phone: phone ?? this.phone,
mobile: mobile ?? this.mobile,
email: email ?? this.email,
gpsLat: gpsLat ?? this.gpsLat,
gpsLng: gpsLng ?? this.gpsLng,
stripeId: stripeId ?? this.stripeId,
chkDemo: chkDemo ?? this.chkDemo,
chkCopieMailRecu: chkCopieMailRecu ?? this.chkCopieMailRecu,
chkAcceptSms: chkAcceptSms ?? this.chkAcceptSms,
chkActive: chkActive ?? this.chkActive,
);
}
}

View File

@@ -0,0 +1,95 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'client_model.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class ClientModelAdapter extends TypeAdapter<ClientModel> {
@override
final int typeId = 10;
@override
ClientModel read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return ClientModel(
id: fields[0] as int,
name: fields[1] as String,
adresse1: fields[2] as String?,
adresse2: fields[3] as String?,
codePostal: fields[4] as String?,
ville: fields[5] as String?,
fkRegion: fields[6] as int?,
libRegion: fields[7] as String?,
fkType: fields[8] as int?,
phone: fields[9] as String?,
mobile: fields[10] as String?,
email: fields[11] as String?,
gpsLat: fields[12] as String?,
gpsLng: fields[13] as String?,
stripeId: fields[14] as String?,
chkDemo: fields[15] as bool?,
chkCopieMailRecu: fields[16] as bool?,
chkAcceptSms: fields[17] as bool?,
chkActive: fields[18] as bool?,
);
}
@override
void write(BinaryWriter writer, ClientModel obj) {
writer
..writeByte(19)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.name)
..writeByte(2)
..write(obj.adresse1)
..writeByte(3)
..write(obj.adresse2)
..writeByte(4)
..write(obj.codePostal)
..writeByte(5)
..write(obj.ville)
..writeByte(6)
..write(obj.fkRegion)
..writeByte(7)
..write(obj.libRegion)
..writeByte(8)
..write(obj.fkType)
..writeByte(9)
..write(obj.phone)
..writeByte(10)
..write(obj.mobile)
..writeByte(11)
..write(obj.email)
..writeByte(12)
..write(obj.gpsLat)
..writeByte(13)
..write(obj.gpsLng)
..writeByte(14)
..write(obj.stripeId)
..writeByte(15)
..write(obj.chkDemo)
..writeByte(16)
..write(obj.chkCopieMailRecu)
..writeByte(17)
..write(obj.chkAcceptSms)
..writeByte(18)
..write(obj.chkActive);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ClientModelAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View 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,
);
}
}

View 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;
}

View 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,
);
}
}

View 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;
}

View 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)';
}
}

View 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;
}

View File

@@ -0,0 +1,89 @@
import 'package:hive/hive.dart';
part 'region_model.g.dart';
@HiveType(typeId: 7) // Assurez-vous que cet ID est unique
class RegionModel extends HiveObject {
@HiveField(0)
final int id;
@HiveField(1)
final int fkPays;
@HiveField(2)
final String libelle;
@HiveField(3)
final String? libelleLong;
@HiveField(4)
final String? tableOsm;
@HiveField(5)
final String? departements;
@HiveField(6)
final bool chkActive;
RegionModel({
required this.id,
required this.fkPays,
required this.libelle,
this.libelleLong,
this.tableOsm,
this.departements,
this.chkActive = true,
});
// Constructeur de copie
RegionModel copyWith({
int? id,
int? fkPays,
String? libelle,
String? libelleLong,
String? tableOsm,
String? departements,
bool? chkActive,
}) {
return RegionModel(
id: id ?? this.id,
fkPays: fkPays ?? this.fkPays,
libelle: libelle ?? this.libelle,
libelleLong: libelleLong ?? this.libelleLong,
tableOsm: tableOsm ?? this.tableOsm,
departements: departements ?? this.departements,
chkActive: chkActive ?? this.chkActive,
);
}
// Conversion depuis JSON
factory RegionModel.fromJson(Map<String, dynamic> json) {
return RegionModel(
id: json['id'] as int,
fkPays: json['fk_pays'] as int,
libelle: json['libelle'] as String,
libelleLong: json['libelle_long'] as String?,
tableOsm: json['table_osm'] as String?,
departements: json['departements'] as String?,
chkActive: json['chk_active'] == 1 || json['chk_active'] == true,
);
}
// Conversion vers JSON
Map<String, dynamic> toJson() {
return {
'id': id,
'fk_pays': fkPays,
'libelle': libelle,
'libelle_long': libelleLong,
'table_osm': tableOsm,
'departements': departements,
'chk_active': chkActive ? 1 : 0,
};
}
@override
String toString() {
return 'RegionModel(id: $id, libelle: $libelle)';
}
}

View File

@@ -0,0 +1,59 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'region_model.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class RegionModelAdapter extends TypeAdapter<RegionModel> {
@override
final int typeId = 7;
@override
RegionModel read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return RegionModel(
id: fields[0] as int,
fkPays: fields[1] as int,
libelle: fields[2] as String,
libelleLong: fields[3] as String?,
tableOsm: fields[4] as String?,
departements: fields[5] as String?,
chkActive: fields[6] as bool,
);
}
@override
void write(BinaryWriter writer, RegionModel obj) {
writer
..writeByte(7)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.fkPays)
..writeByte(2)
..write(obj.libelle)
..writeByte(3)
..write(obj.libelleLong)
..writeByte(4)
..write(obj.tableOsm)
..writeByte(5)
..write(obj.departements)
..writeByte(6)
..write(obj.chkActive);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is RegionModelAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View 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;
}
}

View 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;
}

View File

@@ -0,0 +1,242 @@
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)
int? fkEntite;
@HiveField(15)
int? fkTitre;
@HiveField(16)
String? phone;
@HiveField(17)
String? mobile;
@HiveField(18)
DateTime? dateNaissance;
@HiveField(19)
DateTime? dateEmbauche;
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.fkEntite,
this.fkTitre,
this.phone,
this.mobile,
this.dateNaissance,
this.dateEmbauche,
});
// 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'] ?? json['fk_role'];
final int role = rawRole is String ? int.parse(rawRole) : rawRole as int;
// Convertir fk_entite en int si présent
final dynamic rawFkEntite = json['fk_entite'];
final int? fkEntite = rawFkEntite != null
? (rawFkEntite is String ? int.parse(rawFkEntite) : rawFkEntite as int)
: null;
// Convertir fk_titre en int si présent
final dynamic rawFkTitre = json['fk_titre'];
final int? fkTitre = rawFkTitre != null
? (rawFkTitre is String ? int.parse(rawFkTitre) : rawFkTitre as int)
: null;
// Traiter les dates si présentes
DateTime? dateNaissance;
if (json['date_naissance'] != null && json['date_naissance'] != '') {
try {
dateNaissance = DateTime.parse(json['date_naissance']);
} catch (e) {
dateNaissance = null;
}
}
DateTime? dateEmbauche;
if (json['date_embauche'] != null && json['date_embauche'] != '') {
try {
dateEmbauche = DateTime.parse(json['date_embauche']);
} catch (e) {
dateEmbauche = null;
}
}
return UserModel(
id: id,
email: json['email'] ?? '',
name: json['name'],
username: json['username'],
firstName: json['first_name'],
role: role,
createdAt: json['created_at'] != null
? DateTime.parse(json['created_at'])
: DateTime.now(),
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'],
fkEntite: fkEntite,
fkTitre: fkTitre,
phone: json['phone'],
mobile: json['mobile'],
dateNaissance: dateNaissance,
dateEmbauche: dateEmbauche,
);
}
// 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,
'fk_entite': fkEntite,
'fk_titre': fkTitre,
'phone': phone,
'mobile': mobile,
'date_naissance': dateNaissance?.toIso8601String(),
'date_embauche': dateEmbauche?.toIso8601String(),
};
}
// 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,
int? fkEntite,
int? fkTitre,
String? phone,
String? mobile,
DateTime? dateNaissance,
DateTime? dateEmbauche,
}) {
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,
fkEntite: fkEntite ?? this.fkEntite,
fkTitre: fkTitre ?? this.fkTitre,
phone: phone ?? this.phone,
mobile: mobile ?? this.mobile,
dateNaissance: dateNaissance ?? this.dateNaissance,
dateEmbauche: dateEmbauche ?? this.dateEmbauche,
);
}
// 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,
);
}
}

View File

@@ -0,0 +1,98 @@
// 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?,
fkEntite: fields[14] as int?,
fkTitre: fields[15] as int?,
phone: fields[16] as String?,
mobile: fields[17] as String?,
dateNaissance: fields[18] as DateTime?,
dateEmbauche: fields[19] as DateTime?,
);
}
@override
void write(BinaryWriter writer, UserModel obj) {
writer
..writeByte(20)
..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.fkEntite)
..writeByte(15)
..write(obj.fkTitre)
..writeByte(16)
..write(obj.phone)
..writeByte(17)
..write(obj.mobile)
..writeByte(18)
..write(obj.dateNaissance)
..writeByte(19)
..write(obj.dateEmbauche);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is UserModelAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@@ -0,0 +1,80 @@
import 'package:hive/hive.dart';
part 'user_sector_model.g.dart';
/// Modèle pour stocker les associations entre utilisateurs et secteurs
///
/// Cette classe représente l'association entre un utilisateur et un secteur,
/// telle que reçue de l'API dans la réponse users_sectors.
@HiveType(
typeId: 7) // Assurez-vous que cet ID est unique parmi vos modèles Hive
class UserSectorModel extends HiveObject {
@HiveField(0)
final int id; // ID de l'utilisateur
@HiveField(1)
final String? firstName;
@HiveField(2)
final String? sectName;
@HiveField(3)
final int fkSector; // ID du secteur
@HiveField(4)
final String? name;
UserSectorModel({
required this.id,
this.firstName,
this.sectName,
required this.fkSector,
this.name,
});
/// Crée un modèle UserSectorModel à partir d'un objet JSON
factory UserSectorModel.fromJson(Map<String, dynamic> json) {
return UserSectorModel(
id: json['id'] is String ? int.parse(json['id']) : json['id'],
firstName: json['first_name'],
sectName: json['sect_name'],
fkSector: json['fk_sector'] is String
? int.parse(json['fk_sector'])
: json['fk_sector'],
name: json['name'],
);
}
/// Convertit le modèle en objet JSON
Map<String, dynamic> toJson() {
return {
'id': id,
'first_name': firstName,
'sect_name': sectName,
'fk_sector': fkSector,
'name': name,
};
}
/// Crée une copie du modèle avec des valeurs potentiellement modifiées
UserSectorModel copyWith({
int? id,
String? firstName,
String? sectName,
int? fkSector,
String? name,
}) {
return UserSectorModel(
id: id ?? this.id,
firstName: firstName ?? this.firstName,
sectName: sectName ?? this.sectName,
fkSector: fkSector ?? this.fkSector,
name: name ?? this.name,
);
}
@override
String toString() {
return 'UserSectorModel(id: $id, firstName: $firstName, sectName: $sectName, fkSector: $fkSector, name: $name)';
}
}

View File

@@ -0,0 +1,53 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'user_sector_model.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class UserSectorModelAdapter extends TypeAdapter<UserSectorModel> {
@override
final int typeId = 7;
@override
UserSectorModel read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return UserSectorModel(
id: fields[0] as int,
firstName: fields[1] as String?,
sectName: fields[2] as String?,
fkSector: fields[3] as int,
name: fields[4] as String?,
);
}
@override
void write(BinaryWriter writer, UserSectorModel obj) {
writer
..writeByte(5)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.firstName)
..writeByte(2)
..write(obj.sectName)
..writeByte(3)
..write(obj.fkSector)
..writeByte(4)
..write(obj.name);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is UserSectorModelAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@@ -0,0 +1,75 @@
/// Modèle pour suivre l'état du chargement des données
class LoadingState {
/// Progression globale (0.0 à 1.0)
final double progress;
/// Description de l'étape en cours
final String? stepDescription;
/// Message principal
final String? message;
/// Indique si le chargement est terminé
final bool isCompleted;
/// Indique si une erreur s'est produite
final bool hasError;
/// Message d'erreur éventuel
final String? errorMessage;
const LoadingState({
this.progress = 0.0,
this.stepDescription,
this.message,
this.isCompleted = false,
this.hasError = false,
this.errorMessage,
});
/// Crée un nouvel état de chargement avec les valeurs mises à jour
LoadingState copyWith({
double? progress,
String? stepDescription,
String? message,
bool? isCompleted,
bool? hasError,
String? errorMessage,
}) {
return LoadingState(
progress: progress ?? this.progress,
stepDescription: stepDescription ?? this.stepDescription,
message: message ?? this.message,
isCompleted: isCompleted ?? this.isCompleted,
hasError: hasError ?? this.hasError,
errorMessage: errorMessage ?? this.errorMessage,
);
}
/// État initial du chargement
static const initial = LoadingState(
progress: 0.0,
message: 'Chargement en cours...',
isCompleted: false,
hasError: false,
);
/// État de chargement terminé avec succès
static const completed = LoadingState(
progress: 1.0,
message: 'Chargement terminé',
isCompleted: true,
hasError: false,
);
/// Crée un état d'erreur
static LoadingState error(String message) {
return LoadingState(
progress: 0.0,
message: 'Erreur de chargement',
errorMessage: message,
isCompleted: true,
hasError: true,
);
}
}

View File

@@ -0,0 +1,295 @@
import 'dart:async';
import 'package:flutter/foundation.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/amicale_model.dart';
class AmicaleRepository extends ChangeNotifier {
// Utilisation de getters lazy pour n'accéder à la boîte que lorsque nécessaire
Box<AmicaleModel> get _amicaleBox =>
Hive.box<AmicaleModel>(AppKeys.amicaleBoxName);
final ApiService _apiService;
bool _isLoading = false;
AmicaleRepository(this._apiService);
// Getters
bool get isLoading => _isLoading;
// 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.amicaleBoxName)) {
debugPrint('Ouverture de la boîte amicale...');
await Hive.openBox<AmicaleModel>(AppKeys.amicaleBoxName);
}
} catch (e) {
debugPrint('Erreur lors de l\'ouverture de la boîte amicale: $e');
throw Exception('Impossible d\'ouvrir la boîte amicale: $e');
}
}
// Récupérer toutes les amicales
List<AmicaleModel> getAllAmicales() {
try {
_ensureBoxIsOpen();
return _amicaleBox.values.toList();
} catch (e) {
debugPrint('Erreur lors de la récupération des amicales: $e');
return [];
}
}
// Récupérer une amicale par son ID
AmicaleModel? getAmicaleById(int id) {
try {
_ensureBoxIsOpen();
return _amicaleBox.get(id);
} catch (e) {
debugPrint('Erreur lors de la récupération de l\'amicale: $e');
return null;
}
}
// Récupérer l'amicale de l'utilisateur connecté (basé sur fkEntite)
AmicaleModel? getAmicaleByUserId(int userId, int fkEntite) {
try {
_ensureBoxIsOpen();
return _amicaleBox.get(fkEntite);
} catch (e) {
debugPrint(
'Erreur lors de la récupération de l\'amicale de l\'utilisateur: $e');
return null;
}
}
// Créer ou mettre à jour une amicale localement
Future<AmicaleModel> saveAmicale(AmicaleModel amicale) async {
await _ensureBoxIsOpen();
await _amicaleBox.put(amicale.id, amicale);
notifyListeners(); // Notifier les changements pour mettre à jour l'UI
return amicale;
}
// Supprimer une amicale localement
Future<void> deleteAmicale(int id) async {
await _ensureBoxIsOpen();
await _amicaleBox.delete(id);
notifyListeners();
}
// Vider la boîte des amicales
Future<void> clearAmicales() async {
await _ensureBoxIsOpen();
await _amicaleBox.clear();
notifyListeners();
}
// Traiter les données des amicales reçues de l'API
Future<void> processAmicalesData(dynamic amicalesData) async {
try {
debugPrint('Traitement des données des amicales...');
debugPrint('Détails amicale: $amicalesData');
// Vérifier que les données sont au bon format
if (amicalesData == null) {
debugPrint('Aucune donnée d\'amicale à traiter');
return;
}
// Vider la boîte avant d'ajouter les nouvelles données
await _ensureBoxIsOpen();
await _amicaleBox.clear();
int count = 0;
// Cas 1: Les données sont une liste d'amicales
if (amicalesData is List) {
for (final amicaleData in amicalesData) {
try {
final amicale = AmicaleModel.fromJson(amicaleData);
await _amicaleBox.put(amicale.id, amicale);
count++;
debugPrint('Amicale traitée: ${amicale.name} (ID: ${amicale.id})');
} catch (e) {
debugPrint('Erreur lors du traitement d\'une amicale: $e');
}
}
}
// Cas 2: Les données sont un objet avec une clé 'data' contenant une liste
else if (amicalesData is Map && amicalesData.containsKey('data')) {
final amicalesList = amicalesData['data'] as List<dynamic>;
for (final amicaleData in amicalesList) {
try {
final amicale = AmicaleModel.fromJson(amicaleData);
await _amicaleBox.put(amicale.id, amicale);
count++;
debugPrint('Amicale traitée: ${amicale.name} (ID: ${amicale.id})');
} catch (e) {
debugPrint('Erreur lors du traitement d\'une amicale: $e');
}
}
}
// Cas 3: Les données sont un objet amicale unique (pas une liste)
else if (amicalesData is Map) {
try {
// Convertir Map<dynamic, dynamic> en Map<String, dynamic>
final Map<String, dynamic> amicaleMap = {};
amicalesData.forEach((key, value) {
if (key is String) {
amicaleMap[key] = value;
}
});
final amicale = AmicaleModel.fromJson(amicaleMap);
await _amicaleBox.put(amicale.id, amicale);
count++;
debugPrint(
'Amicale unique traitée: ${amicale.name} (ID: ${amicale.id})');
} catch (e) {
debugPrint('Erreur lors du traitement de l\'amicale unique: $e');
debugPrint('Exception détaillée: $e');
}
} else {
debugPrint('Format de données d\'amicale non reconnu');
return;
}
debugPrint('$count amicales traitées et stockées');
notifyListeners();
} catch (e) {
debugPrint('Erreur lors du traitement des amicales: $e');
}
}
// Récupérer les amicales depuis l'API
Future<List<AmicaleModel>> fetchAmicalesFromApi() async {
_isLoading = true;
notifyListeners();
try {
final response = await _apiService.get('/amicales');
if (response.statusCode == 200) {
final amicalesData = response.data;
await processAmicalesData(amicalesData);
return getAllAmicales();
} else {
debugPrint(
'Erreur lors de la récupération des amicales: ${response.statusCode}');
return [];
}
} catch (e) {
debugPrint('Erreur lors de la récupération des amicales: $e');
return [];
} finally {
_isLoading = false;
notifyListeners();
}
}
// Récupérer une amicale spécifique depuis l'API
Future<AmicaleModel?> fetchAmicaleByIdFromApi(int id) async {
_isLoading = true;
notifyListeners();
try {
final response = await _apiService.get('/amicales/$id');
if (response.statusCode == 200) {
final amicaleData = response.data;
final amicale = AmicaleModel.fromJson(amicaleData);
await saveAmicale(amicale);
return amicale;
} else {
debugPrint(
'Erreur lors de la récupération de l\'amicale: ${response.statusCode}');
return null;
}
} catch (e) {
debugPrint('Erreur lors de la récupération de l\'amicale: $e');
return null;
} finally {
_isLoading = false;
notifyListeners();
}
}
// Mettre à jour une amicale via l'API
Future<AmicaleModel?> updateAmicaleViaApi(AmicaleModel amicale) async {
_isLoading = true;
notifyListeners();
try {
final response = await _apiService.put(
'/amicales/${amicale.id}',
data: amicale.toJson(),
);
if (response.statusCode == 200) {
final updatedAmicaleData = response.data;
final updatedAmicale = AmicaleModel.fromJson(updatedAmicaleData);
await saveAmicale(updatedAmicale);
return updatedAmicale;
} else {
debugPrint(
'Erreur lors de la mise à jour de l\'amicale: ${response.statusCode}');
return null;
}
} catch (e) {
debugPrint('Erreur lors de la mise à jour de l\'amicale: $e');
return null;
} finally {
_isLoading = false;
notifyListeners();
}
}
// Filtrer les amicales par nom
List<AmicaleModel> searchAmicalesByName(String query) {
if (query.isEmpty) {
return getAllAmicales();
}
final lowercaseQuery = query.toLowerCase();
return _amicaleBox.values
.where((amicale) => amicale.name.toLowerCase().contains(lowercaseQuery))
.toList();
}
// Filtrer les amicales par type
List<AmicaleModel> getAmicalesByType(int type) {
return _amicaleBox.values
.where((amicale) => amicale.fkType == type)
.toList();
}
// Filtrer les amicales par région
List<AmicaleModel> getAmicalesByRegion(int regionId) {
return _amicaleBox.values
.where((amicale) => amicale.fkRegion == regionId)
.toList();
}
// Filtrer les amicales actives
List<AmicaleModel> getActiveAmicales() {
return _amicaleBox.values.where((amicale) => amicale.chkActive).toList();
}
// Filtrer les amicales par code postal
List<AmicaleModel> getAmicalesByPostalCode(String postalCode) {
return _amicaleBox.values
.where((amicale) => amicale.codePostal == postalCode)
.toList();
}
// Filtrer les amicales par ville
List<AmicaleModel> getAmicalesByCity(String city) {
final lowercaseCity = city.toLowerCase();
return _amicaleBox.values
.where((amicale) => amicale.ville.toLowerCase().contains(lowercaseCity))
.toList();
}
}

View File

@@ -0,0 +1,179 @@
import 'dart:async';
import 'package:flutter/foundation.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/client_model.dart';
class ClientRepository extends ChangeNotifier {
// Utilisation de getters lazy pour n'accéder à la boîte que lorsque nécessaire
Box<ClientModel> get _clientBox =>
Hive.box<ClientModel>(AppKeys.clientsBoxName);
final ApiService _apiService;
bool _isLoading = false;
ClientRepository(this._apiService);
// Getters
bool get isLoading => _isLoading;
// 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.clientsBoxName)) {
debugPrint('Ouverture de la boîte clients...');
await Hive.openBox<ClientModel>(AppKeys.clientsBoxName);
}
} catch (e) {
debugPrint('Erreur lors de l\'ouverture de la boîte clients: $e');
throw Exception('Impossible d\'ouvrir la boîte clients: $e');
}
}
// Récupérer tous les clients
List<ClientModel> getAllClients() {
try {
_ensureBoxIsOpen();
return _clientBox.values.toList();
} catch (e) {
debugPrint('Erreur lors de la récupération des clients: $e');
return [];
}
}
// Récupérer un client par son ID
ClientModel? getClientById(int id) {
try {
_ensureBoxIsOpen();
return _clientBox.get(id);
} catch (e) {
debugPrint('Erreur lors de la récupération du client: $e');
return null;
}
}
// Créer ou mettre à jour un client localement
Future<ClientModel> saveClient(ClientModel client) async {
await _ensureBoxIsOpen();
await _clientBox.put(client.id, client);
notifyListeners(); // Notifier les changements pour mettre à jour l'UI
return client;
}
// Supprimer un client localement
Future<void> deleteClient(int id) async {
await _ensureBoxIsOpen();
await _clientBox.delete(id);
notifyListeners();
}
// Vider la boîte des clients
Future<void> clearClients() async {
await _ensureBoxIsOpen();
await _clientBox.clear();
notifyListeners();
}
// Traiter les données des clients reçues de l'API
Future<void> processClientsData(dynamic clientsData) async {
try {
debugPrint('Traitement des données des clients...');
// Vérifier que les données sont au bon format
if (clientsData == null) {
debugPrint('Aucune donnée de client à traiter');
return;
}
List<dynamic> clientsList;
if (clientsData is List) {
clientsList = clientsData;
} else if (clientsData is Map && clientsData.containsKey('data')) {
clientsList = clientsData['data'] as List<dynamic>;
} else {
debugPrint('Format de données de clients non reconnu');
return;
}
// Vider la boîte avant d'ajouter les nouvelles données
await _ensureBoxIsOpen();
await _clientBox.clear();
// Traiter chaque client
int count = 0;
for (final clientData in clientsList) {
try {
final client = ClientModel.fromJson(clientData);
await _clientBox.put(client.id, client);
count++;
debugPrint('Client traité: ${client.name} (ID: ${client.id})');
} catch (e) {
debugPrint('Erreur lors du traitement d\'un client: $e');
}
}
debugPrint('$count clients traités et stockés');
notifyListeners();
} catch (e) {
debugPrint('Erreur lors du traitement des clients: $e');
}
}
// Récupérer les clients depuis l'API
Future<List<ClientModel>> fetchClientsFromApi() async {
_isLoading = true;
notifyListeners();
try {
final response = await _apiService.get('/clients');
if (response.statusCode == 200) {
final clientsData = response.data;
await processClientsData(clientsData);
return getAllClients();
} else {
debugPrint(
'Erreur lors de la récupération des clients: ${response.statusCode}');
return [];
}
} catch (e) {
debugPrint('Erreur lors de la récupération des clients: $e');
return [];
} finally {
_isLoading = false;
notifyListeners();
}
}
// Filtrer les clients par nom
List<ClientModel> searchClientsByName(String query) {
if (query.isEmpty) {
return getAllClients();
}
final lowercaseQuery = query.toLowerCase();
return _clientBox.values
.where((client) => client.name.toLowerCase().contains(lowercaseQuery))
.toList();
}
// Filtrer les clients par type
List<ClientModel> getClientsByType(int type) {
return _clientBox.values.where((client) => client.fkType == type).toList();
}
// Filtrer les clients par région
List<ClientModel> getClientsByRegion(int regionId) {
return _clientBox.values
.where((client) => client.fkRegion == regionId)
.toList();
}
// Filtrer les clients actifs
List<ClientModel> getActiveClients() {
return _clientBox.values
.where((client) => client.chkActive == true)
.toList();
}
}

View 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();
}
}
}

View 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();
}
}
}

View File

@@ -0,0 +1,502 @@
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>? _box;
Box<PassageModel> get _passageBox {
if (_box != null && _box!.isOpen) {
return _box!;
}
if (!Hive.isBoxOpen(AppKeys.passagesBoxName)) {
throw StateError(
'La boîte ${AppKeys.passagesBoxName} n\'est pas ouverte. Appelez _ensureBoxIsOpen() avant d\'accéder à la boîte.');
}
_box = Hive.box<PassageModel>(AppKeys.passagesBoxName);
return _box!;
}
// 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;
// Si nous avons déjà une référence à la boîte et qu'elle est ouverte, retourner
if (_box != null && _box!.isOpen) {
return;
}
// Si la boîte est déjà ouverte, récupérer la référence
if (Hive.isBoxOpen(boxName)) {
_box = Hive.box<PassageModel>(boxName);
debugPrint(
'PassageRepository: Boîte $boxName déjà ouverte, référence récupérée');
return;
}
// Sinon, ouvrir la boîte
try {
debugPrint('PassageRepository: Ouverture de la boîte $boxName...');
_box = await Hive.openBox<PassageModel>(boxName);
debugPrint('PassageRepository: Boîte $boxName ouverte avec succès');
} catch (e) {
debugPrint(
'PassageRepository: ERREUR lors de l\'ouverture de la boîte $boxName: $e');
rethrow; // Propager l'erreur pour permettre une gestion appropriée
}
}
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() {
try {
// S'assurer que la boîte est ouverte avant d'y accéder
_ensureBoxIsOpen().then((_) {
debugPrint(
'PassageRepository: Boîte ouverte avec succès pour getAllPassages');
}).catchError((e) {
debugPrint(
'PassageRepository: Erreur lors de l\'ouverture de la boîte pour getAllPassages: $e');
});
return _passageBox.values.toList();
} catch (e) {
debugPrint('PassageRepository: Erreur dans getAllPassages: $e');
return []; // Retourner une liste vide en cas d'erreur
}
}
// Récupérer un passage par son ID
PassageModel? getPassageById(int id) {
try {
// S'assurer que la boîte est ouverte avant d'y accéder
_ensureBoxIsOpen().then((_) {
debugPrint(
'PassageRepository: Boîte ouverte avec succès pour getPassageById');
}).catchError((e) {
debugPrint(
'PassageRepository: Erreur lors de l\'ouverture de la boîte pour getPassageById: $e');
});
return _passageBox.get(id);
} catch (e) {
debugPrint('PassageRepository: Erreur dans getPassageById: $e');
return null;
}
}
// Récupérer les passages par secteur
List<PassageModel> getPassagesBySector(int sectorId) {
try {
// S'assurer que la boîte est ouverte avant d'y accéder
_ensureBoxIsOpen().then((_) {
debugPrint(
'PassageRepository: Boîte ouverte avec succès pour getPassagesBySector');
}).catchError((e) {
debugPrint(
'PassageRepository: Erreur lors de l\'ouverture de la boîte pour getPassagesBySector: $e');
});
return _passageBox.values
.where((passage) => passage.fkSector == sectorId)
.toList();
} catch (e) {
debugPrint('PassageRepository: Erreur dans getPassagesBySector: $e');
return [];
}
}
// Récupérer les passages par opération
List<PassageModel> getPassagesByOperation(int operationId) {
try {
// S'assurer que la boîte est ouverte avant d'y accéder
_ensureBoxIsOpen().then((_) {
debugPrint(
'PassageRepository: Boîte ouverte avec succès pour getPassagesByOperation');
}).catchError((e) {
debugPrint(
'PassageRepository: Erreur lors de l\'ouverture de la boîte pour getPassagesByOperation: $e');
});
return _passageBox.values
.where((passage) => passage.fkOperation == operationId)
.toList();
} catch (e) {
debugPrint('PassageRepository: Erreur dans getPassagesByOperation: $e');
return [];
}
}
// Récupérer les passages par type
List<PassageModel> getPassagesByType(int typeId) {
try {
// S'assurer que la boîte est ouverte avant d'y accéder
_ensureBoxIsOpen().then((_) {
debugPrint(
'PassageRepository: Boîte ouverte avec succès pour getPassagesByType');
}).catchError((e) {
debugPrint(
'PassageRepository: Erreur lors de l\'ouverture de la boîte pour getPassagesByType: $e');
});
return _passageBox.values
.where((passage) => passage.fkType == typeId)
.toList();
} catch (e) {
debugPrint('PassageRepository: Erreur dans getPassagesByType: $e');
return [];
}
}
// Récupérer les passages par type de règlement
List<PassageModel> getPassagesByPaymentType(int paymentTypeId) {
try {
// S'assurer que la boîte est ouverte avant d'y accéder
_ensureBoxIsOpen().then((_) {
debugPrint(
'PassageRepository: Boîte ouverte avec succès pour getPassagesByPaymentType');
}).catchError((e) {
debugPrint(
'PassageRepository: Erreur lors de l\'ouverture de la boîte pour getPassagesByPaymentType: $e');
});
return _passageBox.values
.where((passage) => passage.fkTypeReglement == paymentTypeId)
.toList();
} catch (e) {
debugPrint('PassageRepository: Erreur dans getPassagesByPaymentType: $e');
return [];
}
}
// 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();
}
}

View File

@@ -0,0 +1,85 @@
import 'package:flutter/foundation.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
import 'package:geosector_app/core/data/models/region_model.dart';
import 'package:hive_flutter/hive_flutter.dart';
class RegionRepository extends ChangeNotifier {
late Box<RegionModel> _regionBox;
List<RegionModel> _regions = [];
bool _isLoaded = false;
// Getter pour les régions
List<RegionModel> get regions => _regions;
bool get isLoaded => _isLoaded;
// Initialisation du repository
Future<void> init() async {
if (!Hive.isBoxOpen(AppKeys.regionsBoxName)) {
_regionBox = await Hive.openBox<RegionModel>(AppKeys.regionsBoxName);
} else {
_regionBox = Hive.box<RegionModel>(AppKeys.regionsBoxName);
}
_loadRegions();
}
// Chargement des régions depuis la boîte Hive
void _loadRegions() {
_regions = _regionBox.values.toList();
_isLoaded = true;
notifyListeners();
}
// Mise à jour des régions depuis l'API
Future<void> updateRegionsFromApi(List<dynamic> regionsData) async {
await _regionBox.clear();
for (var regionData in regionsData) {
final region = RegionModel.fromJson(regionData);
await _regionBox.put(region.id, region);
}
_loadRegions();
}
// Récupérer une région par son ID
RegionModel? getRegionById(int id) {
return _regionBox.get(id);
}
// Récupérer une région par son code postal (2 premiers chiffres)
RegionModel? getRegionByPostalCode(String postalCode) {
if (postalCode.length < 2) return null;
final departement = postalCode.substring(0, 2);
for (var region in _regions) {
if (region.departements != null &&
region.departements!.split(',').contains(departement)) {
return region;
}
}
return null;
}
// Récupérer toutes les régions actives
List<RegionModel> getActiveRegions() {
return _regions.where((region) => region.chkActive).toList();
}
// Convertir les régions en format pour le dropdown
List<Map<String, dynamic>> getRegionsForDropdown() {
return _regions
.where((region) => region.chkActive)
.map((region) => {
'id': region.id,
'name': region.libelle,
})
.toList();
}
// Fermeture de la boîte Hive
Future<void> close() async {
await _regionBox.close();
}
}

View 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;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,269 @@
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';
import 'package:universal_html/html.dart' as html;
class ApiService {
final Dio _dio = Dio();
late final String _baseUrl;
late final String _appIdentifier;
String? _sessionId;
// Détermine l'environnement actuel (DEV, REC, PROD) en fonction de l'URL
String _determineEnvironment() {
if (!kIsWeb) {
// En mode non-web, utiliser l'environnement de développement par défaut
return 'DEV';
}
final currentUrl = html.window.location.href.toLowerCase();
if (currentUrl.contains('dapp.geosector.fr')) {
return 'DEV';
} else if (currentUrl.contains('rapp.geosector.fr')) {
return 'REC';
} else {
return 'PROD';
}
}
// Configure l'URL de base API et l'identifiant d'application selon l'environnement
void _configureEnvironment() {
final env = _determineEnvironment();
switch (env) {
case 'DEV':
_baseUrl = AppKeys.baseApiUrlDev;
_appIdentifier = AppKeys.appIdentifierDev;
break;
case 'REC':
_baseUrl = AppKeys.baseApiUrlRec;
_appIdentifier = AppKeys.appIdentifierRec;
break;
default: // PROD
_baseUrl = AppKeys.baseApiUrlProd;
_appIdentifier = AppKeys.appIdentifierProd;
}
debugPrint('GEOSECTOR 🔗 Environnement: $env, API: $_baseUrl');
}
ApiService() {
// Configurer l'environnement
_configureEnvironment();
// Configurer Dio
_dio.options.baseUrl = _baseUrl;
_dio.options.connectTimeout = AppKeys.connectionTimeout;
_dio.options.receiveTimeout = AppKeys.receiveTimeout;
// Ajouter les en-têtes par défaut avec l'identifiant d'application adapté à l'environnement
final headers = Map<String, String>.from(AppKeys.defaultHeaders);
headers['X-App-Identifier'] = _appIdentifier;
_dio.options.headers.addAll(headers);
// 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;
}
// Obtenir l'environnement actuel (utile pour le débogage)
String getCurrentEnvironment() {
return _determineEnvironment();
}
// Obtenir l'URL API actuelle (utile pour le débogage)
String getCurrentApiUrl() {
return _baseUrl;
}
// Obtenir l'identifiant d'application actuel (utile pour le débogage)
String getCurrentAppIdentifier() {
return _appIdentifier;
}
// 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, {required String type}) 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;
}
}
}

View File

@@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.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,
{required String type}) 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
/// et redirection vers la page de démarrage
Future<bool> logout(BuildContext context) async {
final bool result = await LoadingOverlay.show(
context: context,
spinnerSize: 80.0, // Spinner plus grand
strokeWidth: 6.0, // Trait plus épais
future: _userRepository.logout(),
);
// Si la déconnexion a réussi, rediriger vers la page de démarrage
if (result && context.mounted) {
// Utiliser GoRouter pour naviguer vers la page de démarrage
GoRouter.of(context).go('/');
}
return result;
}
/// Vérifie si un utilisateur est connecté
bool isLoggedIn() {
return _userRepository.isLoggedIn;
}
/// Récupère le rôle de l'utilisateur connecté
int getUserRole() {
return _userRepository.getUserRole();
}
}

View 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();
}
}

View File

@@ -0,0 +1,149 @@
import 'package:flutter/foundation.dart';
import 'package:hive_flutter/hive_flutter.dart';
// Importations conditionnelles pour le web vs non-web
import 'js_interface.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
import 'package:geosector_app/core/services/hive_web_fix.dart';
import 'package:geosector_app/core/data/models/user_model.dart';
import 'package:geosector_app/core/data/models/amicale_model.dart';
import 'package:geosector_app/core/data/models/client_model.dart';
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/data/models/user_sector_model.dart';
import 'package:geosector_app/core/data/models/region_model.dart';
import 'package:geosector_app/chat/models/chat_adapters.dart';
/// Service pour réinitialiser et recréer les Hive Boxes
/// Utilisé pour résoudre les problèmes d'incompatibilité après mise à jour des modèles
class HiveResetService {
/// Réinitialise complètement Hive et recrée les boîtes nécessaires
static Future<bool> resetAndRecreateHiveBoxes() async {
try {
debugPrint(
'HiveResetService: Début de la réinitialisation complète de Hive');
// Approche plus radicale pour le web : supprimer directement IndexedDB
if (kIsWeb) {
// Utiliser JavaScript pour supprimer complètement la base de données IndexedDB
evalJs('''
(function() {
return new Promise(function(resolve, reject) {
// Fermer toutes les connexions IndexedDB
if (window.indexedDB) {
console.log("Suppression complète d'IndexedDB...");
var request = indexedDB.deleteDatabase("geosector_app");
request.onsuccess = function() {
console.log("IndexedDB supprimé avec succès");
resolve(true);
};
request.onerror = function(event) {
console.log("Erreur lors de la suppression d'IndexedDB", event);
reject(event);
};
} else {
console.log("IndexedDB n'est pas disponible");
resolve(false);
}
});
})();
''');
// Attendre un peu pour s'assurer que la suppression est terminée
await Future.delayed(const Duration(milliseconds: 1000));
// Réinitialiser Hive
await Hive.initFlutter();
} else {
// Pour les plateformes mobiles, on utilise une approche différente
await Hive.deleteFromDisk();
await Hive.initFlutter();
}
// Réenregistrer tous les adaptateurs
_registerAdapters();
// Rouvrir les boîtes essentielles
await _reopenEssentialBoxes();
debugPrint(
'HiveResetService: Réinitialisation complète terminée avec succès');
return true;
} catch (e) {
debugPrint('HiveResetService: Erreur lors de la réinitialisation: $e');
return false;
}
}
/// Ferme toutes les boîtes Hive ouvertes
static Future<void> _closeAllBoxes() async {
final boxNames = [
AppKeys.usersBoxName,
AppKeys.amicaleBoxName,
AppKeys.clientsBoxName,
AppKeys.operationsBoxName,
AppKeys.sectorsBoxName,
AppKeys.passagesBoxName,
AppKeys.settingsBoxName,
AppKeys.membresBoxName,
AppKeys.userSectorBoxName,
AppKeys.chatConversationsBoxName,
AppKeys.chatMessagesBoxName,
AppKeys.regionsBoxName,
];
for (final boxName in boxNames) {
if (Hive.isBoxOpen(boxName)) {
debugPrint('HiveResetService: Fermeture de la boîte $boxName');
await Hive.box(boxName).close();
}
}
}
/// Enregistre tous les adaptateurs Hive
static void _registerAdapters() {
debugPrint('HiveResetService: Enregistrement des adaptateurs Hive');
// Enregistrer les adaptateurs pour les modèles principaux
Hive.registerAdapter(UserModelAdapter());
Hive.registerAdapter(AmicaleModelAdapter());
Hive.registerAdapter(ClientModelAdapter());
Hive.registerAdapter(OperationModelAdapter());
Hive.registerAdapter(SectorModelAdapter());
Hive.registerAdapter(PassageModelAdapter());
Hive.registerAdapter(MembreModelAdapter());
Hive.registerAdapter(UserSectorModelAdapter());
// Enregistrer les adaptateurs pour le chat
Hive.registerAdapter(ConversationModelAdapter());
Hive.registerAdapter(MessageModelAdapter());
Hive.registerAdapter(ParticipantModelAdapter());
Hive.registerAdapter(AnonymousUserModelAdapter());
Hive.registerAdapter(AudienceTargetModelAdapter());
Hive.registerAdapter(NotificationSettingsAdapter());
// Vérifier si RegionModelAdapter est disponible
try {
Hive.registerAdapter(RegionModelAdapter());
} catch (e) {
debugPrint('HiveResetService: RegionModelAdapter non disponible: $e');
}
}
/// Rouvre les boîtes essentielles
static Future<void> _reopenEssentialBoxes() async {
debugPrint('HiveResetService: Réouverture des boîtes essentielles');
// Ouvrir les boîtes essentielles au démarrage
await Hive.openBox<UserModel>(AppKeys.usersBoxName);
await Hive.openBox<AmicaleModel>(AppKeys.amicaleBoxName);
await Hive.openBox<ClientModel>(AppKeys.clientsBoxName);
await Hive.openBox(AppKeys.settingsBoxName);
// Ouvrir les boîtes de chat
await Hive.openBox<ConversationModel>(AppKeys.chatConversationsBoxName);
await Hive.openBox<MessageModel>(AppKeys.chatMessagesBoxName);
}
}

View File

@@ -0,0 +1,40 @@
import 'package:flutter/foundation.dart';
/// Service pour gérer l'état de réinitialisation de Hive
/// Permet de stocker l'information indiquant si Hive a été réinitialisé
/// et de notifier les widgets intéressés
class HiveResetStateService extends ChangeNotifier {
/// Indique si Hive a été réinitialisé
bool _wasReset = false;
/// Indique si le dialogue de réinitialisation a déjà été affiché
bool _dialogShown = false;
/// Getter pour savoir si Hive a été réinitialisé
bool get wasReset => _wasReset;
/// Getter pour savoir si le dialogue a déjà été affiché
bool get dialogShown => _dialogShown;
/// Marque Hive comme ayant été réinitialisé
void markAsReset() {
_wasReset = true;
notifyListeners();
}
/// Marque le dialogue comme ayant été affiché
void markDialogAsShown() {
_dialogShown = true;
notifyListeners();
}
/// Réinitialise l'état (à utiliser après une déconnexion par exemple)
void reset() {
_wasReset = false;
_dialogShown = false;
notifyListeners();
}
}
/// Instance globale du service
final hiveResetStateService = HiveResetStateService();

View 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');
}
}
}

View File

@@ -0,0 +1,20 @@
/// Interface pour les fonctionnalités JavaScript
/// Importe conditionnellement dart:js pour le web ou un stub pour les autres plateformes
library js_interface;
import 'package:flutter/foundation.dart';
// Importation conditionnelle basée sur la plateforme
import 'js_stub.dart' if (dart.library.js) 'dart:js' as js;
/// Exporte le contexte JavaScript pour être utilisé dans d'autres fichiers
final context = js.context;
/// Fonction utilitaire pour évaluer du code JavaScript sur le web
/// Ne fait rien sur les plateformes non-web
dynamic evalJs(String code) {
if (kIsWeb) {
return js.context.callMethod('eval', [code]);
}
return null;
}

View File

@@ -0,0 +1,11 @@
/// Stub pour dart:js pour les plateformes non-web
/// Fournit une implémentation vide des fonctionnalités de dart:js
class JsContext {
dynamic callMethod(String method, [List<dynamic>? args]) {
// Ne fait rien sur les plateformes non-web
return null;
}
}
/// Contexte JavaScript stub
final JsContext context = JsContext();

View 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');
}
}
}

View 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;
}
}

View 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();
}
}

View File

@@ -0,0 +1,190 @@
import 'package:flutter/material.dart';
class AppTheme {
// Couleurs du thème basées sur la maquette Figma
static const Color primaryColor = Color(0xFF20335E); // Bleu foncé
static const Color secondaryColor = Color(0xFF9DC7C8); // Bleu clair
static const Color accentColor = Color(0xFF00E09D); // Vert
static const Color errorColor = Color(0xFFE41B13); // Rouge
static const Color warningColor = Color(0xFFF7A278); // Orange
static const Color backgroundLightColor =
Color(0xFFF4F5F6); // Gris très clair
static const Color backgroundDarkColor = Color(0xFF111827);
static const Color textLightColor = Color(0xFF000000); // Noir
static const Color textDarkColor = Color(0xFFF9FAFB);
// Thème clair
static ThemeData get lightTheme {
return ThemeData(
useMaterial3: true,
brightness: Brightness.light,
fontFamily: 'Figtree', // Utilisation directe de la police locale
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: const TextTheme().copyWith(
displayLarge: const TextStyle(fontFamily: 'Figtree'),
displayMedium: const TextStyle(fontFamily: 'Figtree'),
displaySmall: const TextStyle(fontFamily: 'Figtree'),
headlineLarge: const TextStyle(fontFamily: 'Figtree'),
headlineMedium: const TextStyle(fontFamily: 'Figtree'),
headlineSmall: const TextStyle(fontFamily: 'Figtree'),
titleLarge: const TextStyle(fontFamily: 'Figtree'),
titleMedium: const TextStyle(fontFamily: 'Figtree'),
titleSmall: const TextStyle(fontFamily: 'Figtree'),
bodyLarge: const TextStyle(fontFamily: 'Figtree'),
bodyMedium: const TextStyle(fontFamily: 'Figtree'),
bodySmall: const TextStyle(fontFamily: 'Figtree'),
labelLarge: const TextStyle(fontFamily: 'Figtree'),
labelMedium: const TextStyle(fontFamily: 'Figtree'),
labelSmall: const TextStyle(fontFamily: 'Figtree'),
),
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: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
textStyle: const TextStyle(
fontFamily: 'Figtree',
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: backgroundLightColor,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: textLightColor.withOpacity(0.1),
width: 1,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: textLightColor.withOpacity(0.1),
width: 1,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
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,
fontFamily: 'Figtree', // Utilisation directe de la police locale
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: const TextTheme().copyWith(
displayLarge: const TextStyle(fontFamily: 'Figtree'),
displayMedium: const TextStyle(fontFamily: 'Figtree'),
displaySmall: const TextStyle(fontFamily: 'Figtree'),
headlineLarge: const TextStyle(fontFamily: 'Figtree'),
headlineMedium: const TextStyle(fontFamily: 'Figtree'),
headlineSmall: const TextStyle(fontFamily: 'Figtree'),
titleLarge: const TextStyle(fontFamily: 'Figtree'),
titleMedium: const TextStyle(fontFamily: 'Figtree'),
titleSmall: const TextStyle(fontFamily: 'Figtree'),
bodyLarge: const TextStyle(fontFamily: 'Figtree'),
bodyMedium: const TextStyle(fontFamily: 'Figtree'),
bodySmall: const TextStyle(fontFamily: 'Figtree'),
labelLarge: const TextStyle(fontFamily: 'Figtree'),
labelMedium: const TextStyle(fontFamily: 'Figtree'),
labelSmall: const TextStyle(fontFamily: 'Figtree'),
),
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: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
textStyle: const TextStyle(
fontFamily: 'Figtree',
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: const Color(0xFF374151),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: textDarkColor.withOpacity(0.1),
width: 1,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: textDarkColor.withOpacity(0.1),
width: 1,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
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),
),
);
}
}