Restructuration majeure du projet: migration de flutt vers app, ajout de l'API et mise à jour du site web
This commit is contained in:
192
app/lib/core/constants/app_keys.dart
Normal file
192
app/lib/core/constants/app_keys.dart
Normal 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,
|
||||
},
|
||||
};
|
||||
}
|
||||
14
app/lib/core/constants/reponse-login.json
Normal file
14
app/lib/core/constants/reponse-login.json
Normal file
File diff suppressed because one or more lines are too long
249
app/lib/core/data/models/amicale_model.dart
Normal file
249
app/lib/core/data/models/amicale_model.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
104
app/lib/core/data/models/amicale_model.g.dart
Normal file
104
app/lib/core/data/models/amicale_model.g.dart
Normal 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;
|
||||
}
|
||||
200
app/lib/core/data/models/client_model.dart
Normal file
200
app/lib/core/data/models/client_model.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
95
app/lib/core/data/models/client_model.g.dart
Normal file
95
app/lib/core/data/models/client_model.g.dart
Normal 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;
|
||||
}
|
||||
137
app/lib/core/data/models/membre_model.dart
Normal file
137
app/lib/core/data/models/membre_model.dart
Normal file
@@ -0,0 +1,137 @@
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'membre_model.g.dart';
|
||||
|
||||
@HiveType(typeId: 5) // Utilisation d'un typeId unique
|
||||
class MembreModel extends HiveObject {
|
||||
@HiveField(0)
|
||||
final int id;
|
||||
|
||||
@HiveField(1)
|
||||
final int fkRole;
|
||||
|
||||
@HiveField(2)
|
||||
final int fkTitre;
|
||||
|
||||
@HiveField(3)
|
||||
final String firstName;
|
||||
|
||||
@HiveField(4)
|
||||
final String? sectName;
|
||||
|
||||
@HiveField(5)
|
||||
final DateTime? dateNaissance;
|
||||
|
||||
@HiveField(6)
|
||||
final DateTime? dateEmbauche;
|
||||
|
||||
@HiveField(7)
|
||||
final int chkActive;
|
||||
|
||||
@HiveField(8)
|
||||
final String name;
|
||||
|
||||
@HiveField(9)
|
||||
final String username;
|
||||
|
||||
@HiveField(10)
|
||||
final String email;
|
||||
|
||||
MembreModel({
|
||||
required this.id,
|
||||
required this.fkRole,
|
||||
required this.fkTitre,
|
||||
required this.firstName,
|
||||
this.sectName,
|
||||
this.dateNaissance,
|
||||
this.dateEmbauche,
|
||||
required this.chkActive,
|
||||
required this.name,
|
||||
required this.username,
|
||||
required this.email,
|
||||
});
|
||||
|
||||
// Factory pour convertir depuis JSON (API)
|
||||
factory MembreModel.fromJson(Map<String, dynamic> json) {
|
||||
// Convertir l'ID en int, qu'il soit déjà int ou string
|
||||
final dynamic rawId = json['id'];
|
||||
final int id = rawId is String ? int.parse(rawId) : rawId as int;
|
||||
|
||||
// Convertir le rôle en int, qu'il soit déjà int ou string
|
||||
final dynamic rawRole = json['fk_role'];
|
||||
final int fkRole = rawRole is String ? int.parse(rawRole) : rawRole as int;
|
||||
|
||||
// Convertir le titre en int, qu'il soit déjà int ou string
|
||||
final dynamic rawTitre = json['fk_titre'];
|
||||
final int fkTitre =
|
||||
rawTitre is String ? int.parse(rawTitre) : rawTitre as int;
|
||||
|
||||
// Convertir le chkActive en int, qu'il soit déjà int ou string
|
||||
final dynamic rawActive = json['chk_active'];
|
||||
final int chkActive =
|
||||
rawActive is String ? int.parse(rawActive) : rawActive as int;
|
||||
|
||||
return MembreModel(
|
||||
id: id,
|
||||
fkRole: fkRole,
|
||||
fkTitre: fkTitre,
|
||||
firstName: json['first_name'] ?? '',
|
||||
sectName: json['sect_name'],
|
||||
dateNaissance: json['date_naissance'] != null
|
||||
? DateTime.parse(json['date_naissance'])
|
||||
: null,
|
||||
dateEmbauche: json['date_embauche'] != null
|
||||
? DateTime.parse(json['date_embauche'])
|
||||
: null,
|
||||
chkActive: chkActive,
|
||||
name: json['name'] ?? '',
|
||||
username: json['username'] ?? '',
|
||||
email: json['email'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
// Convertir en JSON pour l'API
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'fk_role': fkRole,
|
||||
'fk_titre': fkTitre,
|
||||
'first_name': firstName,
|
||||
'sect_name': sectName,
|
||||
'date_naissance': dateNaissance?.toIso8601String(),
|
||||
'date_embauche': dateEmbauche?.toIso8601String(),
|
||||
'chk_active': chkActive,
|
||||
'name': name,
|
||||
'username': username,
|
||||
'email': email,
|
||||
};
|
||||
}
|
||||
|
||||
// Copier avec de nouvelles valeurs
|
||||
MembreModel copyWith({
|
||||
int? fkRole,
|
||||
int? fkTitre,
|
||||
String? firstName,
|
||||
String? sectName,
|
||||
DateTime? dateNaissance,
|
||||
DateTime? dateEmbauche,
|
||||
int? chkActive,
|
||||
String? name,
|
||||
String? username,
|
||||
String? email,
|
||||
}) {
|
||||
return MembreModel(
|
||||
id: this.id,
|
||||
fkRole: fkRole ?? this.fkRole,
|
||||
fkTitre: fkTitre ?? this.fkTitre,
|
||||
firstName: firstName ?? this.firstName,
|
||||
sectName: sectName ?? this.sectName,
|
||||
dateNaissance: dateNaissance ?? this.dateNaissance,
|
||||
dateEmbauche: dateEmbauche ?? this.dateEmbauche,
|
||||
chkActive: chkActive ?? this.chkActive,
|
||||
name: name ?? this.name,
|
||||
username: username ?? this.username,
|
||||
email: email ?? this.email,
|
||||
);
|
||||
}
|
||||
}
|
||||
71
app/lib/core/data/models/membre_model.g.dart
Normal file
71
app/lib/core/data/models/membre_model.g.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'membre_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class MembreModelAdapter extends TypeAdapter<MembreModel> {
|
||||
@override
|
||||
final int typeId = 5;
|
||||
|
||||
@override
|
||||
MembreModel read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return MembreModel(
|
||||
id: fields[0] as int,
|
||||
fkRole: fields[1] as int,
|
||||
fkTitre: fields[2] as int,
|
||||
firstName: fields[3] as String,
|
||||
sectName: fields[4] as String?,
|
||||
dateNaissance: fields[5] as DateTime?,
|
||||
dateEmbauche: fields[6] as DateTime?,
|
||||
chkActive: fields[7] as int,
|
||||
name: fields[8] as String,
|
||||
username: fields[9] as String,
|
||||
email: fields[10] as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, MembreModel obj) {
|
||||
writer
|
||||
..writeByte(11)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.fkRole)
|
||||
..writeByte(2)
|
||||
..write(obj.fkTitre)
|
||||
..writeByte(3)
|
||||
..write(obj.firstName)
|
||||
..writeByte(4)
|
||||
..write(obj.sectName)
|
||||
..writeByte(5)
|
||||
..write(obj.dateNaissance)
|
||||
..writeByte(6)
|
||||
..write(obj.dateEmbauche)
|
||||
..writeByte(7)
|
||||
..write(obj.chkActive)
|
||||
..writeByte(8)
|
||||
..write(obj.name)
|
||||
..writeByte(9)
|
||||
..write(obj.username)
|
||||
..writeByte(10)
|
||||
..write(obj.email);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is MembreModelAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
85
app/lib/core/data/models/operation_model.dart
Normal file
85
app/lib/core/data/models/operation_model.dart
Normal file
@@ -0,0 +1,85 @@
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'operation_model.g.dart';
|
||||
|
||||
@HiveType(typeId: 1)
|
||||
class OperationModel extends HiveObject {
|
||||
@HiveField(0)
|
||||
final int id;
|
||||
|
||||
@HiveField(1)
|
||||
final String name;
|
||||
|
||||
@HiveField(2)
|
||||
final DateTime dateDebut;
|
||||
|
||||
@HiveField(3)
|
||||
final DateTime dateFin;
|
||||
|
||||
@HiveField(4)
|
||||
DateTime lastSyncedAt;
|
||||
|
||||
@HiveField(5)
|
||||
bool isActive;
|
||||
|
||||
@HiveField(6)
|
||||
bool isSynced;
|
||||
|
||||
OperationModel({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.dateDebut,
|
||||
required this.dateFin,
|
||||
required this.lastSyncedAt,
|
||||
this.isActive = true,
|
||||
this.isSynced = false,
|
||||
});
|
||||
|
||||
// Factory pour convertir depuis JSON (API)
|
||||
factory OperationModel.fromJson(Map<String, dynamic> json) {
|
||||
// Convertir l'ID en int, qu'il soit déjà int ou string
|
||||
final dynamic rawId = json['id'];
|
||||
final int id = rawId is String ? int.parse(rawId) : rawId as int;
|
||||
|
||||
return OperationModel(
|
||||
id: id,
|
||||
name: json['name'],
|
||||
dateDebut: DateTime.parse(json['date_deb']),
|
||||
dateFin: DateTime.parse(json['date_fin']),
|
||||
lastSyncedAt: DateTime.now(),
|
||||
isActive: true,
|
||||
isSynced: true,
|
||||
);
|
||||
}
|
||||
|
||||
// Convertir en JSON pour l'API
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'date_deb': dateDebut.toIso8601String().split('T')[0], // Format YYYY-MM-DD
|
||||
'date_fin': dateFin.toIso8601String().split('T')[0], // Format YYYY-MM-DD
|
||||
'is_active': isActive,
|
||||
};
|
||||
}
|
||||
|
||||
// Copier avec de nouvelles valeurs
|
||||
OperationModel copyWith({
|
||||
String? name,
|
||||
DateTime? dateDebut,
|
||||
DateTime? dateFin,
|
||||
bool? isActive,
|
||||
bool? isSynced,
|
||||
DateTime? lastSyncedAt,
|
||||
}) {
|
||||
return OperationModel(
|
||||
id: this.id,
|
||||
name: name ?? this.name,
|
||||
dateDebut: dateDebut ?? this.dateDebut,
|
||||
dateFin: dateFin ?? this.dateFin,
|
||||
lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt,
|
||||
isActive: isActive ?? this.isActive,
|
||||
isSynced: isSynced ?? this.isSynced,
|
||||
);
|
||||
}
|
||||
}
|
||||
59
app/lib/core/data/models/operation_model.g.dart
Normal file
59
app/lib/core/data/models/operation_model.g.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'operation_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class OperationModelAdapter extends TypeAdapter<OperationModel> {
|
||||
@override
|
||||
final int typeId = 1;
|
||||
|
||||
@override
|
||||
OperationModel read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return OperationModel(
|
||||
id: fields[0] as int,
|
||||
name: fields[1] as String,
|
||||
dateDebut: fields[2] as DateTime,
|
||||
dateFin: fields[3] as DateTime,
|
||||
lastSyncedAt: fields[4] as DateTime,
|
||||
isActive: fields[5] as bool,
|
||||
isSynced: fields[6] as bool,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, OperationModel obj) {
|
||||
writer
|
||||
..writeByte(7)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.name)
|
||||
..writeByte(2)
|
||||
..write(obj.dateDebut)
|
||||
..writeByte(3)
|
||||
..write(obj.dateFin)
|
||||
..writeByte(4)
|
||||
..write(obj.lastSyncedAt)
|
||||
..writeByte(5)
|
||||
..write(obj.isActive)
|
||||
..writeByte(6)
|
||||
..write(obj.isSynced);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is OperationModelAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
291
app/lib/core/data/models/passage_model.dart
Normal file
291
app/lib/core/data/models/passage_model.dart
Normal file
@@ -0,0 +1,291 @@
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'passage_model.g.dart';
|
||||
|
||||
@HiveType(typeId: 4)
|
||||
class PassageModel extends HiveObject {
|
||||
@HiveField(0)
|
||||
final int id;
|
||||
|
||||
@HiveField(1)
|
||||
final int fkOperation;
|
||||
|
||||
@HiveField(2)
|
||||
final int fkSector;
|
||||
|
||||
@HiveField(3)
|
||||
final int fkUser;
|
||||
|
||||
@HiveField(4)
|
||||
final int fkType;
|
||||
|
||||
@HiveField(5)
|
||||
final String fkAdresse;
|
||||
|
||||
@HiveField(6)
|
||||
final DateTime passedAt;
|
||||
|
||||
@HiveField(7)
|
||||
final String numero;
|
||||
|
||||
@HiveField(8)
|
||||
final String rue;
|
||||
|
||||
@HiveField(9)
|
||||
final String rueBis;
|
||||
|
||||
@HiveField(10)
|
||||
final String ville;
|
||||
|
||||
@HiveField(11)
|
||||
final String residence;
|
||||
|
||||
@HiveField(12)
|
||||
final int fkHabitat;
|
||||
|
||||
@HiveField(13)
|
||||
final String appt;
|
||||
|
||||
@HiveField(14)
|
||||
final String niveau;
|
||||
|
||||
@HiveField(15)
|
||||
final String gpsLat;
|
||||
|
||||
@HiveField(16)
|
||||
final String gpsLng;
|
||||
|
||||
@HiveField(17)
|
||||
final String nomRecu;
|
||||
|
||||
@HiveField(18)
|
||||
final String remarque;
|
||||
|
||||
@HiveField(19)
|
||||
final String montant;
|
||||
|
||||
@HiveField(20)
|
||||
final int fkTypeReglement;
|
||||
|
||||
@HiveField(21)
|
||||
final String emailErreur;
|
||||
|
||||
@HiveField(22)
|
||||
final int nbPassages;
|
||||
|
||||
@HiveField(23)
|
||||
final String name;
|
||||
|
||||
@HiveField(24)
|
||||
final String email;
|
||||
|
||||
@HiveField(25)
|
||||
final String phone;
|
||||
|
||||
@HiveField(26)
|
||||
DateTime lastSyncedAt;
|
||||
|
||||
@HiveField(27)
|
||||
bool isActive;
|
||||
|
||||
@HiveField(28)
|
||||
bool isSynced;
|
||||
|
||||
PassageModel({
|
||||
required this.id,
|
||||
required this.fkOperation,
|
||||
required this.fkSector,
|
||||
required this.fkUser,
|
||||
required this.fkType,
|
||||
required this.fkAdresse,
|
||||
required this.passedAt,
|
||||
required this.numero,
|
||||
required this.rue,
|
||||
this.rueBis = '',
|
||||
required this.ville,
|
||||
this.residence = '',
|
||||
required this.fkHabitat,
|
||||
this.appt = '',
|
||||
this.niveau = '',
|
||||
required this.gpsLat,
|
||||
required this.gpsLng,
|
||||
this.nomRecu = '',
|
||||
this.remarque = '',
|
||||
required this.montant,
|
||||
required this.fkTypeReglement,
|
||||
this.emailErreur = '',
|
||||
required this.nbPassages,
|
||||
required this.name,
|
||||
this.email = '',
|
||||
this.phone = '',
|
||||
required this.lastSyncedAt,
|
||||
this.isActive = true,
|
||||
this.isSynced = false,
|
||||
});
|
||||
|
||||
// Factory pour convertir depuis JSON (API)
|
||||
factory PassageModel.fromJson(Map<String, dynamic> json) {
|
||||
// Convertir l'ID en int, qu'il soit déjà int ou string
|
||||
final dynamic rawId = json['id'];
|
||||
final int id = rawId is String ? int.parse(rawId) : rawId as int;
|
||||
|
||||
// Convertir les autres champs numériques
|
||||
final dynamic rawFkOperation = json['fk_operation'];
|
||||
final int fkOperation = rawFkOperation is String ? int.parse(rawFkOperation) : rawFkOperation as int;
|
||||
|
||||
final dynamic rawFkSector = json['fk_sector'];
|
||||
final int fkSector = rawFkSector is String ? int.parse(rawFkSector) : rawFkSector as int;
|
||||
|
||||
final dynamic rawFkUser = json['fk_user'];
|
||||
final int fkUser = rawFkUser is String ? int.parse(rawFkUser) : rawFkUser as int;
|
||||
|
||||
final dynamic rawFkType = json['fk_type'];
|
||||
final int fkType = rawFkType is String ? int.parse(rawFkType) : rawFkType as int;
|
||||
|
||||
final dynamic rawFkHabitat = json['fk_habitat'];
|
||||
final int fkHabitat = rawFkHabitat is String ? int.parse(rawFkHabitat) : rawFkHabitat as int;
|
||||
|
||||
final dynamic rawFkTypeReglement = json['fk_type_reglement'];
|
||||
final int fkTypeReglement = rawFkTypeReglement is String ? int.parse(rawFkTypeReglement) : rawFkTypeReglement as int;
|
||||
|
||||
final dynamic rawNbPassages = json['nb_passages'];
|
||||
final int nbPassages = rawNbPassages is String ? int.parse(rawNbPassages) : rawNbPassages as int;
|
||||
|
||||
// Convertir la date
|
||||
final DateTime passedAt = DateTime.parse(json['passed_at']);
|
||||
|
||||
return PassageModel(
|
||||
id: id,
|
||||
fkOperation: fkOperation,
|
||||
fkSector: fkSector,
|
||||
fkUser: fkUser,
|
||||
fkType: fkType,
|
||||
fkAdresse: json['fk_adresse'] as String,
|
||||
passedAt: passedAt,
|
||||
numero: json['numero'] as String,
|
||||
rue: json['rue'] as String,
|
||||
rueBis: json['rue_bis'] as String? ?? '',
|
||||
ville: json['ville'] as String,
|
||||
residence: json['residence'] as String? ?? '',
|
||||
fkHabitat: fkHabitat,
|
||||
appt: json['appt'] as String? ?? '',
|
||||
niveau: json['niveau'] as String? ?? '',
|
||||
gpsLat: json['gps_lat'] as String,
|
||||
gpsLng: json['gps_lng'] as String,
|
||||
nomRecu: json['nom_recu'] as String? ?? '',
|
||||
remarque: json['remarque'] as String? ?? '',
|
||||
montant: json['montant'] as String,
|
||||
fkTypeReglement: fkTypeReglement,
|
||||
emailErreur: json['email_erreur'] as String? ?? '',
|
||||
nbPassages: nbPassages,
|
||||
name: json['name'] as String,
|
||||
email: json['email'] as String? ?? '',
|
||||
phone: json['phone'] as String? ?? '',
|
||||
lastSyncedAt: DateTime.now(),
|
||||
isActive: true,
|
||||
isSynced: true,
|
||||
);
|
||||
}
|
||||
|
||||
// Convertir en JSON pour l'API
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'fk_operation': fkOperation,
|
||||
'fk_sector': fkSector,
|
||||
'fk_user': fkUser,
|
||||
'fk_type': fkType,
|
||||
'fk_adresse': fkAdresse,
|
||||
'passed_at': passedAt.toIso8601String(),
|
||||
'numero': numero,
|
||||
'rue': rue,
|
||||
'rue_bis': rueBis,
|
||||
'ville': ville,
|
||||
'residence': residence,
|
||||
'fk_habitat': fkHabitat,
|
||||
'appt': appt,
|
||||
'niveau': niveau,
|
||||
'gps_lat': gpsLat,
|
||||
'gps_lng': gpsLng,
|
||||
'nom_recu': nomRecu,
|
||||
'remarque': remarque,
|
||||
'montant': montant,
|
||||
'fk_type_reglement': fkTypeReglement,
|
||||
'email_erreur': emailErreur,
|
||||
'nb_passages': nbPassages,
|
||||
'name': name,
|
||||
'email': email,
|
||||
'phone': phone,
|
||||
};
|
||||
}
|
||||
|
||||
// Copier avec de nouvelles valeurs
|
||||
PassageModel copyWith({
|
||||
int? id,
|
||||
int? fkOperation,
|
||||
int? fkSector,
|
||||
int? fkUser,
|
||||
int? fkType,
|
||||
String? fkAdresse,
|
||||
DateTime? passedAt,
|
||||
String? numero,
|
||||
String? rue,
|
||||
String? rueBis,
|
||||
String? ville,
|
||||
String? residence,
|
||||
int? fkHabitat,
|
||||
String? appt,
|
||||
String? niveau,
|
||||
String? gpsLat,
|
||||
String? gpsLng,
|
||||
String? nomRecu,
|
||||
String? remarque,
|
||||
String? montant,
|
||||
int? fkTypeReglement,
|
||||
String? emailErreur,
|
||||
int? nbPassages,
|
||||
String? name,
|
||||
String? email,
|
||||
String? phone,
|
||||
DateTime? lastSyncedAt,
|
||||
bool? isActive,
|
||||
bool? isSynced,
|
||||
}) {
|
||||
return PassageModel(
|
||||
id: id ?? this.id,
|
||||
fkOperation: fkOperation ?? this.fkOperation,
|
||||
fkSector: fkSector ?? this.fkSector,
|
||||
fkUser: fkUser ?? this.fkUser,
|
||||
fkType: fkType ?? this.fkType,
|
||||
fkAdresse: fkAdresse ?? this.fkAdresse,
|
||||
passedAt: passedAt ?? this.passedAt,
|
||||
numero: numero ?? this.numero,
|
||||
rue: rue ?? this.rue,
|
||||
rueBis: rueBis ?? this.rueBis,
|
||||
ville: ville ?? this.ville,
|
||||
residence: residence ?? this.residence,
|
||||
fkHabitat: fkHabitat ?? this.fkHabitat,
|
||||
appt: appt ?? this.appt,
|
||||
niveau: niveau ?? this.niveau,
|
||||
gpsLat: gpsLat ?? this.gpsLat,
|
||||
gpsLng: gpsLng ?? this.gpsLng,
|
||||
nomRecu: nomRecu ?? this.nomRecu,
|
||||
remarque: remarque ?? this.remarque,
|
||||
montant: montant ?? this.montant,
|
||||
fkTypeReglement: fkTypeReglement ?? this.fkTypeReglement,
|
||||
emailErreur: emailErreur ?? this.emailErreur,
|
||||
nbPassages: nbPassages ?? this.nbPassages,
|
||||
name: name ?? this.name,
|
||||
email: email ?? this.email,
|
||||
phone: phone ?? this.phone,
|
||||
lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt,
|
||||
isActive: isActive ?? this.isActive,
|
||||
isSynced: isSynced ?? this.isSynced,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PassageModel(id: $id, fkOperation: $fkOperation, fkSector: $fkSector, fkUser: $fkUser, fkType: $fkType, adresse: $fkAdresse, ville: $ville, montant: $montant)';
|
||||
}
|
||||
}
|
||||
125
app/lib/core/data/models/passage_model.g.dart
Normal file
125
app/lib/core/data/models/passage_model.g.dart
Normal file
@@ -0,0 +1,125 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'passage_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class PassageModelAdapter extends TypeAdapter<PassageModel> {
|
||||
@override
|
||||
final int typeId = 4;
|
||||
|
||||
@override
|
||||
PassageModel read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return PassageModel(
|
||||
id: fields[0] as int,
|
||||
fkOperation: fields[1] as int,
|
||||
fkSector: fields[2] as int,
|
||||
fkUser: fields[3] as int,
|
||||
fkType: fields[4] as int,
|
||||
fkAdresse: fields[5] as String,
|
||||
passedAt: fields[6] as DateTime,
|
||||
numero: fields[7] as String,
|
||||
rue: fields[8] as String,
|
||||
rueBis: fields[9] as String,
|
||||
ville: fields[10] as String,
|
||||
residence: fields[11] as String,
|
||||
fkHabitat: fields[12] as int,
|
||||
appt: fields[13] as String,
|
||||
niveau: fields[14] as String,
|
||||
gpsLat: fields[15] as String,
|
||||
gpsLng: fields[16] as String,
|
||||
nomRecu: fields[17] as String,
|
||||
remarque: fields[18] as String,
|
||||
montant: fields[19] as String,
|
||||
fkTypeReglement: fields[20] as int,
|
||||
emailErreur: fields[21] as String,
|
||||
nbPassages: fields[22] as int,
|
||||
name: fields[23] as String,
|
||||
email: fields[24] as String,
|
||||
phone: fields[25] as String,
|
||||
lastSyncedAt: fields[26] as DateTime,
|
||||
isActive: fields[27] as bool,
|
||||
isSynced: fields[28] as bool,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, PassageModel obj) {
|
||||
writer
|
||||
..writeByte(29)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.fkOperation)
|
||||
..writeByte(2)
|
||||
..write(obj.fkSector)
|
||||
..writeByte(3)
|
||||
..write(obj.fkUser)
|
||||
..writeByte(4)
|
||||
..write(obj.fkType)
|
||||
..writeByte(5)
|
||||
..write(obj.fkAdresse)
|
||||
..writeByte(6)
|
||||
..write(obj.passedAt)
|
||||
..writeByte(7)
|
||||
..write(obj.numero)
|
||||
..writeByte(8)
|
||||
..write(obj.rue)
|
||||
..writeByte(9)
|
||||
..write(obj.rueBis)
|
||||
..writeByte(10)
|
||||
..write(obj.ville)
|
||||
..writeByte(11)
|
||||
..write(obj.residence)
|
||||
..writeByte(12)
|
||||
..write(obj.fkHabitat)
|
||||
..writeByte(13)
|
||||
..write(obj.appt)
|
||||
..writeByte(14)
|
||||
..write(obj.niveau)
|
||||
..writeByte(15)
|
||||
..write(obj.gpsLat)
|
||||
..writeByte(16)
|
||||
..write(obj.gpsLng)
|
||||
..writeByte(17)
|
||||
..write(obj.nomRecu)
|
||||
..writeByte(18)
|
||||
..write(obj.remarque)
|
||||
..writeByte(19)
|
||||
..write(obj.montant)
|
||||
..writeByte(20)
|
||||
..write(obj.fkTypeReglement)
|
||||
..writeByte(21)
|
||||
..write(obj.emailErreur)
|
||||
..writeByte(22)
|
||||
..write(obj.nbPassages)
|
||||
..writeByte(23)
|
||||
..write(obj.name)
|
||||
..writeByte(24)
|
||||
..write(obj.email)
|
||||
..writeByte(25)
|
||||
..write(obj.phone)
|
||||
..writeByte(26)
|
||||
..write(obj.lastSyncedAt)
|
||||
..writeByte(27)
|
||||
..write(obj.isActive)
|
||||
..writeByte(28)
|
||||
..write(obj.isSynced);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is PassageModelAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
89
app/lib/core/data/models/region_model.dart
Normal file
89
app/lib/core/data/models/region_model.dart
Normal 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)';
|
||||
}
|
||||
}
|
||||
59
app/lib/core/data/models/region_model.g.dart
Normal file
59
app/lib/core/data/models/region_model.g.dart
Normal 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;
|
||||
}
|
||||
85
app/lib/core/data/models/sector_model.dart
Normal file
85
app/lib/core/data/models/sector_model.dart
Normal file
@@ -0,0 +1,85 @@
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'sector_model.g.dart';
|
||||
|
||||
@HiveType(typeId: 3)
|
||||
class SectorModel extends HiveObject {
|
||||
@HiveField(0)
|
||||
final int id;
|
||||
|
||||
@HiveField(1)
|
||||
final String libelle;
|
||||
|
||||
@HiveField(2)
|
||||
final String color;
|
||||
|
||||
@HiveField(3)
|
||||
final String sector;
|
||||
|
||||
SectorModel({
|
||||
required this.id,
|
||||
required this.libelle,
|
||||
required this.color,
|
||||
required this.sector,
|
||||
});
|
||||
|
||||
// Factory pour convertir depuis JSON (API)
|
||||
factory SectorModel.fromJson(Map<String, dynamic> json) {
|
||||
return SectorModel(
|
||||
id: json['id'] is String ? int.parse(json['id']) : json['id'] as int,
|
||||
libelle: json['libelle'] as String,
|
||||
color: json['color'] as String,
|
||||
sector: json['sector'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
// Convertir en JSON pour l'API
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'libelle': libelle,
|
||||
'color': color,
|
||||
'sector': sector,
|
||||
};
|
||||
}
|
||||
|
||||
// Copier avec de nouvelles valeurs
|
||||
SectorModel copyWith({
|
||||
int? id,
|
||||
String? libelle,
|
||||
String? color,
|
||||
String? sector,
|
||||
}) {
|
||||
return SectorModel(
|
||||
id: id ?? this.id,
|
||||
libelle: libelle ?? this.libelle,
|
||||
color: color ?? this.color,
|
||||
sector: sector ?? this.sector,
|
||||
);
|
||||
}
|
||||
|
||||
// Obtenir les coordonnées du secteur sous forme de liste de points
|
||||
List<List<double>> getCoordinates() {
|
||||
final List<List<double>> coordinates = [];
|
||||
|
||||
// Le format est "lat1/lng1#lat2/lng2#lat3/lng3#..."
|
||||
final List<String> points = sector.split('#');
|
||||
|
||||
for (final String point in points) {
|
||||
if (point.isEmpty) continue;
|
||||
|
||||
final List<String> latLng = point.split('/');
|
||||
if (latLng.length == 2) {
|
||||
try {
|
||||
final double lat = double.parse(latLng[0]);
|
||||
final double lng = double.parse(latLng[1]);
|
||||
coordinates.add([lat, lng]);
|
||||
} catch (e) {
|
||||
// Ignorer les points mal formatés
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return coordinates;
|
||||
}
|
||||
}
|
||||
50
app/lib/core/data/models/sector_model.g.dart
Normal file
50
app/lib/core/data/models/sector_model.g.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'sector_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class SectorModelAdapter extends TypeAdapter<SectorModel> {
|
||||
@override
|
||||
final int typeId = 3;
|
||||
|
||||
@override
|
||||
SectorModel read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return SectorModel(
|
||||
id: fields[0] as int,
|
||||
libelle: fields[1] as String,
|
||||
color: fields[2] as String,
|
||||
sector: fields[3] as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, SectorModel obj) {
|
||||
writer
|
||||
..writeByte(4)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.libelle)
|
||||
..writeByte(2)
|
||||
..write(obj.color)
|
||||
..writeByte(3)
|
||||
..write(obj.sector);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is SectorModelAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
242
app/lib/core/data/models/user_model.dart
Normal file
242
app/lib/core/data/models/user_model.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
98
app/lib/core/data/models/user_model.g.dart
Normal file
98
app/lib/core/data/models/user_model.g.dart
Normal 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;
|
||||
}
|
||||
80
app/lib/core/data/models/user_sector_model.dart
Normal file
80
app/lib/core/data/models/user_sector_model.dart
Normal 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)';
|
||||
}
|
||||
}
|
||||
53
app/lib/core/data/models/user_sector_model.g.dart
Normal file
53
app/lib/core/data/models/user_sector_model.g.dart
Normal 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;
|
||||
}
|
||||
75
app/lib/core/models/loading_state.dart
Normal file
75
app/lib/core/models/loading_state.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
295
app/lib/core/repositories/amicale_repository.dart
Normal file
295
app/lib/core/repositories/amicale_repository.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
179
app/lib/core/repositories/client_repository.dart
Normal file
179
app/lib/core/repositories/client_repository.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
208
app/lib/core/repositories/membre_repository.dart
Normal file
208
app/lib/core/repositories/membre_repository.dart
Normal file
@@ -0,0 +1,208 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||
import 'package:geosector_app/core/services/api_service.dart';
|
||||
import 'package:geosector_app/core/data/models/membre_model.dart';
|
||||
|
||||
class MembreRepository extends ChangeNotifier {
|
||||
// Utilisation de getters lazy pour n'accéder à la boîte que lorsque nécessaire
|
||||
Box<MembreModel> get _membreBox =>
|
||||
Hive.box<MembreModel>(AppKeys.membresBoxName);
|
||||
|
||||
final ApiService _apiService;
|
||||
bool _isLoading = false;
|
||||
|
||||
MembreRepository(this._apiService);
|
||||
|
||||
// Getters
|
||||
bool get isLoading => _isLoading;
|
||||
List<MembreModel> get membres => getAllMembres();
|
||||
|
||||
// Méthode pour vérifier si une boîte est ouverte et l'ouvrir si nécessaire
|
||||
Future<void> _ensureBoxIsOpen() async {
|
||||
try {
|
||||
if (!Hive.isBoxOpen(AppKeys.membresBoxName)) {
|
||||
debugPrint('Ouverture de la boîte ${AppKeys.membresBoxName}...');
|
||||
await Hive.openBox<MembreModel>(AppKeys.membresBoxName);
|
||||
debugPrint('Boîte ${AppKeys.membresBoxName} ouverte avec succès');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint(
|
||||
'Erreur lors de l\'ouverture de la boîte ${AppKeys.membresBoxName}: $e');
|
||||
throw Exception(
|
||||
'Impossible d\'ouvrir la boîte ${AppKeys.membresBoxName}: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Récupérer tous les membres
|
||||
List<MembreModel> getAllMembres() {
|
||||
try {
|
||||
return _membreBox.values.toList();
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la récupération des membres: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Récupérer un membre par son ID
|
||||
MembreModel? getMembreById(int id) {
|
||||
try {
|
||||
return _membreBox.get(id);
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la récupération du membre: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Créer ou mettre à jour un membre
|
||||
Future<MembreModel> saveMembre(MembreModel membre) async {
|
||||
await _ensureBoxIsOpen();
|
||||
await _membreBox.put(membre.id, membre);
|
||||
notifyListeners();
|
||||
return membre;
|
||||
}
|
||||
|
||||
// Supprimer un membre
|
||||
Future<void> deleteMembre(int id) async {
|
||||
await _ensureBoxIsOpen();
|
||||
await _membreBox.delete(id);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Récupérer les membres depuis l'API (uniquement pour l'interface admin)
|
||||
Future<List<MembreModel>> fetchMembresFromApi() async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final hasConnection = await _apiService.hasInternetConnection();
|
||||
if (!hasConnection) {
|
||||
debugPrint(
|
||||
'Pas de connexion Internet, utilisation des données locales');
|
||||
return getAllMembres();
|
||||
}
|
||||
|
||||
// Endpoint à adapter selon votre API
|
||||
final response = await _apiService.get('/membres');
|
||||
final List<dynamic> membresData = response.data['membres'];
|
||||
|
||||
// Vider la boîte avant d'ajouter les nouveaux membres
|
||||
await _ensureBoxIsOpen();
|
||||
await _membreBox.clear();
|
||||
|
||||
final List<MembreModel> membres = [];
|
||||
for (var membreData in membresData) {
|
||||
try {
|
||||
final membre = MembreModel.fromJson(membreData);
|
||||
await _membreBox.put(membre.id, membre);
|
||||
membres.add(membre);
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors du traitement d\'un membre: $e');
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
return membres;
|
||||
} catch (e) {
|
||||
debugPrint(
|
||||
'Erreur lors de la récupération des membres depuis l\'API: $e');
|
||||
return getAllMembres();
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Créer un membre via l'API
|
||||
Future<MembreModel?> createMembreViaApi(MembreModel membre) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final hasConnection = await _apiService.hasInternetConnection();
|
||||
if (!hasConnection) {
|
||||
debugPrint('Pas de connexion Internet, impossible de créer le membre');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Endpoint à adapter selon votre API
|
||||
final response =
|
||||
await _apiService.post('/membres', data: membre.toJson());
|
||||
final membreData = response.data['membre'];
|
||||
|
||||
final newMembre = MembreModel.fromJson(membreData);
|
||||
await saveMembre(newMembre);
|
||||
|
||||
return newMembre;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la création du membre via l\'API: $e');
|
||||
return null;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Mettre à jour un membre via l'API
|
||||
Future<MembreModel?> updateMembreViaApi(MembreModel membre) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final hasConnection = await _apiService.hasInternetConnection();
|
||||
if (!hasConnection) {
|
||||
debugPrint(
|
||||
'Pas de connexion Internet, impossible de mettre à jour le membre');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Endpoint à adapter selon votre API
|
||||
final response =
|
||||
await _apiService.put('/membres/${membre.id}', data: membre.toJson());
|
||||
final membreData = response.data['membre'];
|
||||
|
||||
final updatedMembre = MembreModel.fromJson(membreData);
|
||||
await saveMembre(updatedMembre);
|
||||
|
||||
return updatedMembre;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la mise à jour du membre via l\'API: $e');
|
||||
return null;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Supprimer un membre via l'API
|
||||
Future<bool> deleteMembreViaApi(int id) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final hasConnection = await _apiService.hasInternetConnection();
|
||||
if (!hasConnection) {
|
||||
debugPrint(
|
||||
'Pas de connexion Internet, impossible de supprimer le membre');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Endpoint à adapter selon votre API
|
||||
await _apiService.delete('/membres/$id');
|
||||
|
||||
// Supprimer localement
|
||||
await deleteMembre(id);
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la suppression du membre via l\'API: $e');
|
||||
return false;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
215
app/lib/core/repositories/operation_repository.dart
Normal file
215
app/lib/core/repositories/operation_repository.dart
Normal file
@@ -0,0 +1,215 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:geosector_app/core/data/models/operation_model.dart';
|
||||
import 'package:geosector_app/core/services/api_service.dart';
|
||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||
|
||||
class OperationRepository extends ChangeNotifier {
|
||||
// Utiliser un getter lazy pour n'accéder à la boîte que lorsque nécessaire
|
||||
// et vérifier qu'elle est ouverte avant accès
|
||||
Box<OperationModel> get _operationBox {
|
||||
_ensureBoxIsOpen();
|
||||
return Hive.box<OperationModel>(AppKeys.operationsBoxName);
|
||||
}
|
||||
|
||||
// Méthode pour vérifier si la boîte est ouverte et l'ouvrir si nécessaire
|
||||
Future<void> _ensureBoxIsOpen() async {
|
||||
final boxName = AppKeys.operationsBoxName;
|
||||
if (!Hive.isBoxOpen(boxName)) {
|
||||
debugPrint('Ouverture de la boîte $boxName dans OperationRepository...');
|
||||
await Hive.openBox<OperationModel>(boxName);
|
||||
}
|
||||
}
|
||||
final ApiService _apiService;
|
||||
|
||||
bool _isLoading = false;
|
||||
|
||||
OperationRepository(this._apiService);
|
||||
|
||||
// Getters
|
||||
bool get isLoading => _isLoading;
|
||||
List<OperationModel> get operations => getAllOperations();
|
||||
|
||||
// Récupérer toutes les opérations
|
||||
List<OperationModel> getAllOperations() {
|
||||
return _operationBox.values.toList();
|
||||
}
|
||||
|
||||
// Récupérer une opération par son ID
|
||||
OperationModel? getOperationById(int id) {
|
||||
return _operationBox.get(id);
|
||||
}
|
||||
|
||||
// Sauvegarder une opération
|
||||
Future<void> saveOperation(OperationModel operation) async {
|
||||
await _operationBox.put(operation.id, operation);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Supprimer une opération
|
||||
Future<void> deleteOperation(int id) async {
|
||||
await _operationBox.delete(id);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Créer ou mettre à jour des opérations à partir des données de l'API
|
||||
Future<void> processOperationsFromApi(List<dynamic> operationsData) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
for (var operationData in operationsData) {
|
||||
final operationJson = operationData as Map<String, dynamic>;
|
||||
final operationId = operationJson['id'] is String
|
||||
? int.parse(operationJson['id'])
|
||||
: operationJson['id'] as int;
|
||||
|
||||
// Vérifier si l'opération existe déjà
|
||||
OperationModel? existingOperation = getOperationById(operationId);
|
||||
|
||||
if (existingOperation == null) {
|
||||
// Créer une nouvelle opération
|
||||
final newOperation = OperationModel.fromJson(operationJson);
|
||||
await saveOperation(newOperation);
|
||||
} else {
|
||||
// Mettre à jour l'opération existante
|
||||
final updatedOperation = existingOperation.copyWith(
|
||||
name: operationJson['name'],
|
||||
dateDebut: DateTime.parse(operationJson['date_deb']),
|
||||
dateFin: DateTime.parse(operationJson['date_fin']),
|
||||
lastSyncedAt: DateTime.now(),
|
||||
isSynced: true,
|
||||
);
|
||||
await saveOperation(updatedOperation);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors du traitement des opérations: $e');
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Créer une opération
|
||||
Future<bool> createOperation(String name, DateTime dateDebut, DateTime dateFin) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
// Préparer les données pour l'API
|
||||
final Map<String, dynamic> data = {
|
||||
'name': name,
|
||||
'date_deb': dateDebut.toIso8601String().split('T')[0], // Format YYYY-MM-DD
|
||||
'date_fin': dateFin.toIso8601String().split('T')[0], // Format YYYY-MM-DD
|
||||
};
|
||||
|
||||
// Appeler l'API pour créer l'opération
|
||||
final response = await _apiService.post('/operations', data: data);
|
||||
|
||||
if (response.statusCode == 201 || response.statusCode == 200) {
|
||||
// Récupérer l'ID de la nouvelle opération
|
||||
final operationId = response.data['id'] is String
|
||||
? int.parse(response.data['id'])
|
||||
: response.data['id'] as int;
|
||||
|
||||
// Créer l'opération localement
|
||||
final newOperation = OperationModel(
|
||||
id: operationId,
|
||||
name: name,
|
||||
dateDebut: dateDebut,
|
||||
dateFin: dateFin,
|
||||
lastSyncedAt: DateTime.now(),
|
||||
isActive: true,
|
||||
isSynced: true,
|
||||
);
|
||||
|
||||
await saveOperation(newOperation);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la création de l\'opération: $e');
|
||||
return false;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Mettre à jour une opération
|
||||
Future<bool> updateOperation(int id, {String? name, DateTime? dateDebut, DateTime? dateFin, bool? isActive}) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
// Récupérer l'opération existante
|
||||
final existingOperation = getOperationById(id);
|
||||
if (existingOperation == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Préparer les données pour l'API
|
||||
final Map<String, dynamic> data = {
|
||||
'id': id,
|
||||
'name': name ?? existingOperation.name,
|
||||
'date_deb': (dateDebut ?? existingOperation.dateDebut).toIso8601String().split('T')[0],
|
||||
'date_fin': (dateFin ?? existingOperation.dateFin).toIso8601String().split('T')[0],
|
||||
'is_active': isActive ?? existingOperation.isActive,
|
||||
};
|
||||
|
||||
// Appeler l'API pour mettre à jour l'opération
|
||||
final response = await _apiService.put('/operations/$id', data: data);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// Mettre à jour l'opération localement
|
||||
final updatedOperation = existingOperation.copyWith(
|
||||
name: name,
|
||||
dateDebut: dateDebut,
|
||||
dateFin: dateFin,
|
||||
isActive: isActive,
|
||||
lastSyncedAt: DateTime.now(),
|
||||
isSynced: true,
|
||||
);
|
||||
|
||||
await saveOperation(updatedOperation);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la mise à jour de l\'opération: $e');
|
||||
return false;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Supprimer une opération via l'API
|
||||
Future<bool> deleteOperationViaApi(int id) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
// Appeler l'API pour supprimer l'opération
|
||||
final response = await _apiService.delete('/operations/$id');
|
||||
|
||||
if (response.statusCode == 200 || response.statusCode == 204) {
|
||||
// Supprimer l'opération localement
|
||||
await deleteOperation(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la suppression de l\'opération: $e');
|
||||
return false;
|
||||
} finally {
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
502
app/lib/core/repositories/passage_repository.dart
Normal file
502
app/lib/core/repositories/passage_repository.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
85
app/lib/core/repositories/region_repository.dart
Normal file
85
app/lib/core/repositories/region_repository.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
149
app/lib/core/repositories/sector_repository.dart
Normal file
149
app/lib/core/repositories/sector_repository.dart
Normal file
@@ -0,0 +1,149 @@
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:geosector_app/core/data/models/sector_model.dart';
|
||||
import 'package:geosector_app/core/services/api_service.dart';
|
||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||
|
||||
class SectorRepository {
|
||||
final ApiService _apiService;
|
||||
|
||||
SectorRepository(this._apiService);
|
||||
|
||||
// Utiliser un getter lazy pour n'accéder à la boîte que lorsque nécessaire
|
||||
// et vérifier qu'elle est ouverte avant accès
|
||||
Box<SectorModel> get _sectorsBox {
|
||||
_ensureBoxIsOpen();
|
||||
return Hive.box<SectorModel>(AppKeys.sectorsBoxName);
|
||||
}
|
||||
|
||||
// Méthode pour vérifier si la boîte est ouverte et l'ouvrir si nécessaire
|
||||
Future<void> _ensureBoxIsOpen() async {
|
||||
final boxName = AppKeys.sectorsBoxName;
|
||||
if (!Hive.isBoxOpen(boxName)) {
|
||||
print('Ouverture de la boîte $boxName dans SectorRepository...');
|
||||
await Hive.openBox<SectorModel>(boxName);
|
||||
}
|
||||
}
|
||||
|
||||
// Récupérer tous les secteurs depuis la base de données locale
|
||||
List<SectorModel> getAllSectors() {
|
||||
return _sectorsBox.values.toList();
|
||||
}
|
||||
|
||||
// Récupérer un secteur par son ID
|
||||
SectorModel? getSectorById(int id) {
|
||||
try {
|
||||
return _sectorsBox.values.firstWhere(
|
||||
(sector) => sector.id == id,
|
||||
);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Sauvegarder les secteurs dans la base de données locale
|
||||
Future<void> saveSectors(List<SectorModel> sectors) async {
|
||||
// Vider la box avant d'ajouter les nouveaux secteurs
|
||||
await _sectorsBox.clear();
|
||||
|
||||
// Ajouter les nouveaux secteurs
|
||||
for (final sector in sectors) {
|
||||
await _sectorsBox.put(sector.id, sector);
|
||||
}
|
||||
}
|
||||
|
||||
// Ajouter ou mettre à jour un secteur
|
||||
Future<void> saveSector(SectorModel sector) async {
|
||||
await _sectorsBox.put(sector.id, sector);
|
||||
}
|
||||
|
||||
// Supprimer un secteur
|
||||
Future<void> deleteSector(int id) async {
|
||||
await _sectorsBox.delete(id);
|
||||
}
|
||||
|
||||
// Récupérer les secteurs depuis l'API
|
||||
Future<List<SectorModel>> fetchSectorsFromApi() async {
|
||||
try {
|
||||
final response = await _apiService.get(AppKeys.sectorsEndpoint);
|
||||
final Map<String, dynamic> responseData = response as Map<String, dynamic>;
|
||||
|
||||
if (responseData['status'] == 'success' && responseData['sectors'] != null) {
|
||||
final List<dynamic> sectorsJson = responseData['sectors'];
|
||||
final List<SectorModel> sectors = sectorsJson
|
||||
.map((json) => SectorModel.fromJson(json))
|
||||
.toList();
|
||||
|
||||
// Sauvegarder les secteurs localement
|
||||
await saveSectors(sectors);
|
||||
|
||||
return sectors;
|
||||
}
|
||||
|
||||
return [];
|
||||
} catch (e) {
|
||||
// En cas d'erreur, retourner les secteurs locaux
|
||||
return getAllSectors();
|
||||
}
|
||||
}
|
||||
|
||||
// Créer un nouveau secteur via l'API
|
||||
Future<SectorModel?> createSector(SectorModel sector) async {
|
||||
try {
|
||||
final response = await _apiService.post(
|
||||
AppKeys.sectorsEndpoint,
|
||||
data: sector.toJson(),
|
||||
);
|
||||
final Map<String, dynamic> responseData = response as Map<String, dynamic>;
|
||||
|
||||
if (responseData['status'] == 'success' && responseData['sector'] != null) {
|
||||
final SectorModel newSector = SectorModel.fromJson(responseData['sector']);
|
||||
await saveSector(newSector);
|
||||
return newSector;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Mettre à jour un secteur via l'API
|
||||
Future<SectorModel?> updateSector(SectorModel sector) async {
|
||||
try {
|
||||
final response = await _apiService.put(
|
||||
'${AppKeys.sectorsEndpoint}/${sector.id}',
|
||||
data: sector.toJson(),
|
||||
);
|
||||
final Map<String, dynamic> responseData = response as Map<String, dynamic>;
|
||||
|
||||
if (responseData['status'] == 'success' && responseData['sector'] != null) {
|
||||
final SectorModel updatedSector = SectorModel.fromJson(responseData['sector']);
|
||||
await saveSector(updatedSector);
|
||||
return updatedSector;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Supprimer un secteur via l'API
|
||||
Future<bool> deleteSectorFromApi(int id) async {
|
||||
try {
|
||||
final response = await _apiService.delete(
|
||||
'${AppKeys.sectorsEndpoint}/$id',
|
||||
);
|
||||
final Map<String, dynamic> responseData = response as Map<String, dynamic>;
|
||||
|
||||
if (responseData['status'] == 'success') {
|
||||
await deleteSector(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
1949
app/lib/core/repositories/user_repository.dart
Normal file
1949
app/lib/core/repositories/user_repository.dart
Normal file
File diff suppressed because it is too large
Load Diff
269
app/lib/core/services/api_service.dart
Normal file
269
app/lib/core/services/api_service.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
51
app/lib/core/services/auth_service.dart
Normal file
51
app/lib/core/services/auth_service.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
157
app/lib/core/services/connectivity_service.dart
Normal file
157
app/lib/core/services/connectivity_service.dart
Normal file
@@ -0,0 +1,157 @@
|
||||
import 'dart:async';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
|
||||
/// Service qui gère la surveillance de l'état de connectivité de l'appareil
|
||||
class ConnectivityService extends ChangeNotifier {
|
||||
final Connectivity _connectivity = Connectivity();
|
||||
late StreamSubscription<List<ConnectivityResult>> _connectivitySubscription;
|
||||
|
||||
List<ConnectivityResult> _connectionStatus = [ConnectivityResult.none];
|
||||
bool _isInitialized = false;
|
||||
|
||||
/// Indique si l'appareil est connecté à Internet
|
||||
bool get isConnected {
|
||||
// Vérifie si la liste contient au moins un type de connexion autre que 'none'
|
||||
return _connectionStatus.any((result) => result != ConnectivityResult.none);
|
||||
}
|
||||
|
||||
/// Indique si l'appareil est connecté via WiFi
|
||||
bool get isWifi => _connectionStatus.contains(ConnectivityResult.wifi);
|
||||
|
||||
/// Indique si l'appareil est connecté via données mobiles (4G, 5G, etc.)
|
||||
bool get isMobile => _connectionStatus.contains(ConnectivityResult.mobile);
|
||||
|
||||
/// Retourne le type de connexion actuel (WiFi, données mobiles, etc.)
|
||||
List<ConnectivityResult> get connectionStatus => _connectionStatus;
|
||||
|
||||
/// Retourne le premier type de connexion actif (pour compatibilité avec l'ancien code)
|
||||
ConnectivityResult get primaryConnectionStatus {
|
||||
// Retourne le premier type de connexion qui n'est pas 'none', ou 'none' si tous sont 'none'
|
||||
return _connectionStatus.firstWhere(
|
||||
(result) => result != ConnectivityResult.none,
|
||||
orElse: () => ConnectivityResult.none
|
||||
);
|
||||
}
|
||||
|
||||
/// Obtient une description textuelle du type de connexion
|
||||
String get connectionType {
|
||||
// Si aucune connexion n'est disponible
|
||||
if (!isConnected) {
|
||||
return 'Aucune connexion';
|
||||
}
|
||||
|
||||
// Utiliser le premier type de connexion actif
|
||||
ConnectivityResult primaryStatus = primaryConnectionStatus;
|
||||
|
||||
switch (primaryStatus) {
|
||||
case ConnectivityResult.wifi:
|
||||
return 'WiFi';
|
||||
case ConnectivityResult.mobile:
|
||||
return 'Données mobiles';
|
||||
case ConnectivityResult.ethernet:
|
||||
return 'Ethernet';
|
||||
case ConnectivityResult.bluetooth:
|
||||
return 'Bluetooth';
|
||||
case ConnectivityResult.vpn:
|
||||
return 'VPN';
|
||||
case ConnectivityResult.none:
|
||||
return 'Aucune connexion';
|
||||
default:
|
||||
return 'Inconnu';
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructeur du service de connectivité
|
||||
ConnectivityService() {
|
||||
_initConnectivity();
|
||||
}
|
||||
|
||||
/// Initialise le service et commence à écouter les changements de connectivité
|
||||
Future<void> _initConnectivity() async {
|
||||
if (_isInitialized) return;
|
||||
|
||||
try {
|
||||
// En version web, on considère par défaut que la connexion est disponible
|
||||
// car la vérification de connectivité est moins fiable sur le web
|
||||
if (kIsWeb) {
|
||||
_connectionStatus = [ConnectivityResult.wifi]; // Valeur par défaut pour le web
|
||||
} else {
|
||||
_connectionStatus = await _connectivity.checkConnectivity();
|
||||
}
|
||||
|
||||
// S'abonner aux changements de connectivité
|
||||
_connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
|
||||
_isInitialized = true;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de l\'initialisation du service de connectivité: $e');
|
||||
|
||||
// En cas d'erreur en version web, on suppose que la connexion est disponible
|
||||
// car l'application web ne peut pas fonctionner sans connexion de toute façon
|
||||
if (kIsWeb) {
|
||||
_connectionStatus = [ConnectivityResult.wifi];
|
||||
_isInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Met à jour l'état de la connexion lorsqu'il change
|
||||
void _updateConnectionStatus(List<ConnectivityResult> results) {
|
||||
// Vérifier si la liste des résultats a changé
|
||||
bool hasChanged = false;
|
||||
|
||||
// Si les listes ont des longueurs différentes, elles sont différentes
|
||||
if (_connectionStatus.length != results.length) {
|
||||
hasChanged = true;
|
||||
} else {
|
||||
// Vérifier si les éléments sont différents
|
||||
for (int i = 0; i < _connectionStatus.length; i++) {
|
||||
if (i >= results.length || _connectionStatus[i] != results[i]) {
|
||||
hasChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasChanged) {
|
||||
_connectionStatus = results;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie manuellement l'état actuel de la connexion
|
||||
Future<List<ConnectivityResult>> checkConnectivity() async {
|
||||
try {
|
||||
// En version web, on considère par défaut que la connexion est disponible
|
||||
if (kIsWeb) {
|
||||
// En version web, on peut tenter de faire une requête réseau légère pour vérifier la connectivité
|
||||
// mais pour l'instant, on suppose que la connexion est disponible
|
||||
final results = [ConnectivityResult.wifi];
|
||||
_updateConnectionStatus(results);
|
||||
return results;
|
||||
} else {
|
||||
// Version mobile - utiliser l'API standard
|
||||
final results = await _connectivity.checkConnectivity();
|
||||
_updateConnectionStatus(results);
|
||||
return results;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la vérification de la connectivité: $e');
|
||||
// En cas d'erreur, on conserve l'état actuel
|
||||
return _connectionStatus;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
try {
|
||||
_connectivitySubscription.cancel();
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de l\'annulation de l\'abonnement de connectivité: $e');
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
149
app/lib/core/services/hive_reset_service.dart
Normal file
149
app/lib/core/services/hive_reset_service.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
40
app/lib/core/services/hive_reset_state_service.dart
Normal file
40
app/lib/core/services/hive_reset_state_service.dart
Normal 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();
|
||||
182
app/lib/core/services/hive_web_fix.dart
Normal file
182
app/lib/core/services/hive_web_fix.dart
Normal file
@@ -0,0 +1,182 @@
|
||||
import 'dart:async';
|
||||
import 'dart:js' as js;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||
|
||||
/// Service pour gérer les problèmes spécifiques à Hive en version web
|
||||
class HiveWebFix {
|
||||
/// Nettoie en toute sécurité les boîtes Hive en version web
|
||||
/// Cette méthode est plus sûre que de supprimer directement IndexedDB
|
||||
static Future<void> safeCleanHiveBoxes({List<String>? excludeBoxes}) async {
|
||||
if (!kIsWeb) return;
|
||||
|
||||
try {
|
||||
debugPrint(
|
||||
'HiveWebFix: Nettoyage sécurisé des boîtes Hive en version web');
|
||||
|
||||
// Liste des boîtes à nettoyer
|
||||
final boxesToClean = [
|
||||
AppKeys.operationsBoxName,
|
||||
AppKeys.sectorsBoxName,
|
||||
AppKeys.passagesBoxName,
|
||||
];
|
||||
|
||||
// Exclure certaines boîtes si spécifié
|
||||
if (excludeBoxes != null) {
|
||||
boxesToClean.removeWhere((box) => excludeBoxes.contains(box));
|
||||
}
|
||||
|
||||
// Nettoyer chaque boîte individuellement au lieu de supprimer IndexedDB
|
||||
for (final boxName in boxesToClean) {
|
||||
try {
|
||||
if (Hive.isBoxOpen(boxName)) {
|
||||
debugPrint('HiveWebFix: Nettoyage de la boîte $boxName');
|
||||
final box = Hive.box(boxName);
|
||||
await box.clear();
|
||||
debugPrint('HiveWebFix: Boîte $boxName nettoyée avec succès');
|
||||
} else {
|
||||
debugPrint(
|
||||
'HiveWebFix: La boîte $boxName n\'est pas ouverte, ouverture temporaire');
|
||||
final box = await Hive.openBox(boxName);
|
||||
await box.clear();
|
||||
await box.close();
|
||||
debugPrint('HiveWebFix: Boîte $boxName nettoyée et fermée');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint(
|
||||
'HiveWebFix: Erreur lors du nettoyage de la boîte $boxName: $e');
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('HiveWebFix: Nettoyage sécurisé terminé');
|
||||
} catch (e) {
|
||||
debugPrint('HiveWebFix: Erreur lors du nettoyage sécurisé: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie l'intégrité des boîtes Hive et tente de les réparer si nécessaire
|
||||
static Future<bool> checkAndRepairHiveBoxes() async {
|
||||
if (!kIsWeb) return true;
|
||||
|
||||
try {
|
||||
debugPrint('HiveWebFix: Vérification de l\'intégrité des boîtes Hive');
|
||||
|
||||
// Vérifier si IndexedDB est accessible
|
||||
final isIndexedDBAvailable = js.context.hasProperty('indexedDB');
|
||||
if (!isIndexedDBAvailable) {
|
||||
debugPrint(
|
||||
'HiveWebFix: IndexedDB n\'est pas disponible dans ce navigateur');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Liste des boîtes essentielles
|
||||
final essentialBoxes = [
|
||||
AppKeys.usersBoxName,
|
||||
AppKeys.settingsBoxName,
|
||||
];
|
||||
|
||||
// Vérifier chaque boîte essentielle
|
||||
for (final boxName in essentialBoxes) {
|
||||
try {
|
||||
if (!Hive.isBoxOpen(boxName)) {
|
||||
debugPrint(
|
||||
'HiveWebFix: Ouverture de la boîte essentielle $boxName');
|
||||
await Hive.openBox(boxName);
|
||||
}
|
||||
|
||||
// Vérifier si la boîte est accessible
|
||||
final box = Hive.box(boxName);
|
||||
// Tenter une opération simple pour vérifier l'intégrité
|
||||
final length = box.length;
|
||||
debugPrint(
|
||||
'HiveWebFix: Boîte $boxName accessible avec $length éléments');
|
||||
} catch (e) {
|
||||
debugPrint('HiveWebFix: Erreur d\'accès à la boîte $boxName: $e');
|
||||
|
||||
// Tenter de réparer en réinitialisant Hive
|
||||
try {
|
||||
debugPrint(
|
||||
'HiveWebFix: Tentative de réparation de la boîte $boxName');
|
||||
// Fermer la boîte si elle est ouverte
|
||||
if (Hive.isBoxOpen(boxName)) {
|
||||
await Hive.box(boxName).close();
|
||||
}
|
||||
|
||||
// Réouvrir la boîte
|
||||
await Hive.openBox(boxName);
|
||||
debugPrint('HiveWebFix: Boîte $boxName réparée avec succès');
|
||||
} catch (repairError) {
|
||||
debugPrint(
|
||||
'HiveWebFix: Échec de la réparation de la boîte $boxName: $repairError');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('HiveWebFix: Toutes les boîtes essentielles sont intègres');
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('HiveWebFix: Erreur lors de la vérification d\'intégrité: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Réinitialise complètement Hive en cas de problème grave
|
||||
/// À utiliser en dernier recours car cela supprimera toutes les données
|
||||
static Future<void> resetHiveCompletely() async {
|
||||
if (!kIsWeb) return;
|
||||
|
||||
try {
|
||||
debugPrint('HiveWebFix: Réinitialisation complète de Hive');
|
||||
|
||||
// Fermer toutes les boîtes ouvertes
|
||||
final boxesToClose = [
|
||||
AppKeys.usersBoxName,
|
||||
AppKeys.operationsBoxName,
|
||||
AppKeys.sectorsBoxName,
|
||||
AppKeys.passagesBoxName,
|
||||
AppKeys.settingsBoxName,
|
||||
];
|
||||
|
||||
for (final boxName in boxesToClose) {
|
||||
if (Hive.isBoxOpen(boxName)) {
|
||||
debugPrint('HiveWebFix: Fermeture de la boîte $boxName');
|
||||
await Hive.box(boxName).close();
|
||||
}
|
||||
}
|
||||
|
||||
// Supprimer IndexedDB avec une approche plus sûre
|
||||
js.context.callMethod('eval', [
|
||||
'''
|
||||
(function() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var request = indexedDB.deleteDatabase("geosector_app");
|
||||
request.onsuccess = function() {
|
||||
console.log("IndexedDB nettoyé avec succès");
|
||||
resolve(true);
|
||||
};
|
||||
request.onerror = function(event) {
|
||||
console.log("Erreur lors du nettoyage d'IndexedDB", event);
|
||||
reject(event);
|
||||
};
|
||||
});
|
||||
})();
|
||||
'''
|
||||
]);
|
||||
|
||||
// Attendre un peu pour s'assurer que la suppression est terminée
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
|
||||
// Réinitialiser Hive
|
||||
await Hive.initFlutter();
|
||||
|
||||
// Réenregistrer les adaptateurs
|
||||
// Note: Cette partie devrait être gérée par le code principal de l'application
|
||||
|
||||
debugPrint('HiveWebFix: Réinitialisation complète terminée');
|
||||
} catch (e) {
|
||||
debugPrint('HiveWebFix: Erreur lors de la réinitialisation complète: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
20
app/lib/core/services/js_interface.dart
Normal file
20
app/lib/core/services/js_interface.dart
Normal 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;
|
||||
}
|
||||
11
app/lib/core/services/js_stub.dart
Normal file
11
app/lib/core/services/js_stub.dart
Normal 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();
|
||||
164
app/lib/core/services/location_service.dart
Normal file
164
app/lib/core/services/location_service.dart
Normal file
@@ -0,0 +1,164 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
/// Service de géolocalisation pour gérer les permissions et l'accès à la position
|
||||
class LocationService {
|
||||
/// Vérifie si les services de localisation sont activés
|
||||
static Future<bool> isLocationServiceEnabled() async {
|
||||
// En version web, on considère que les services de localisation sont toujours activés
|
||||
// car la vérification est gérée différemment par le navigateur
|
||||
if (kIsWeb) {
|
||||
return true;
|
||||
}
|
||||
return await Geolocator.isLocationServiceEnabled();
|
||||
}
|
||||
|
||||
/// Vérifie et demande les permissions de localisation
|
||||
/// Retourne true si l'autorisation est accordée, false sinon
|
||||
static Future<bool> checkAndRequestPermission() async {
|
||||
// En version web, on considère que les permissions sont toujours accordées
|
||||
// car la gestion des permissions est différente et gérée par le navigateur
|
||||
if (kIsWeb) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// Vérifier si les services de localisation sont activés
|
||||
bool serviceEnabled = await isLocationServiceEnabled();
|
||||
if (!serviceEnabled) {
|
||||
// Les services de localisation ne sont pas activés, on ne peut pas demander la permission
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vérifier le statut actuel de la permission
|
||||
LocationPermission permission = await Geolocator.checkPermission();
|
||||
|
||||
if (permission == LocationPermission.denied) {
|
||||
// Demander la permission
|
||||
permission = await Geolocator.requestPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
// La permission a été refusée
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (permission == LocationPermission.deniedForever) {
|
||||
// La permission a été refusée définitivement
|
||||
return false;
|
||||
}
|
||||
|
||||
// La permission est accordée (whileInUse ou always)
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la vérification des permissions de localisation: $e');
|
||||
// En cas d'erreur, on retourne false pour être sûr
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtient la position actuelle de l'utilisateur
|
||||
/// Retourne null si la position ne peut pas être obtenue
|
||||
static Future<LatLng?> getCurrentPosition() async {
|
||||
try {
|
||||
// En version web, la géolocalisation fonctionne différemment
|
||||
// et peut être bloquée par le navigateur si le site n'est pas en HTTPS
|
||||
if (kIsWeb) {
|
||||
try {
|
||||
Position position = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high,
|
||||
);
|
||||
return LatLng(position.latitude, position.longitude);
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de l\'obtention de la position en version web: $e');
|
||||
// En version web, en cas d'erreur, on peut retourner une position par défaut
|
||||
// ou null selon les besoins de l'application
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Version mobile
|
||||
// Vérifier si l'autorisation est accordée
|
||||
bool hasPermission = await checkAndRequestPermission();
|
||||
if (!hasPermission) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Obtenir la position actuelle
|
||||
Position position = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high,
|
||||
);
|
||||
|
||||
return LatLng(position.latitude, position.longitude);
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de l\'obtention de la position: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie si l'application peut accéder à la position de l'utilisateur
|
||||
/// Retourne un message d'erreur si l'accès n'est pas possible, null sinon
|
||||
static Future<String?> getLocationErrorMessage() async {
|
||||
// En version web, on considère qu'il n'y a pas d'erreur de localisation
|
||||
// car la gestion des permissions est gérée par le navigateur
|
||||
if (kIsWeb) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Vérifier si les services de localisation sont activés
|
||||
bool serviceEnabled = await isLocationServiceEnabled();
|
||||
if (!serviceEnabled) {
|
||||
return 'Les services de localisation sont désactivés. Veuillez les activer dans les paramètres de votre appareil.';
|
||||
}
|
||||
|
||||
// Vérifier le statut actuel de la permission
|
||||
LocationPermission permission = await Geolocator.checkPermission();
|
||||
|
||||
if (permission == LocationPermission.denied) {
|
||||
return 'L\'accès à la localisation a été refusé. Cette application ne peut pas fonctionner sans cette autorisation.';
|
||||
}
|
||||
|
||||
if (permission == LocationPermission.deniedForever) {
|
||||
return 'L\'accès à la localisation a été définitivement refusé. Veuillez l\'autoriser dans les paramètres de votre appareil.';
|
||||
}
|
||||
|
||||
return null; // Pas d'erreur
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la vérification des erreurs de localisation: $e');
|
||||
// En cas d'erreur, on retourne null pour ne pas bloquer l'application
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Ouvre les paramètres de l'application pour permettre à l'utilisateur de modifier les autorisations
|
||||
static Future<void> openAppSettings() async {
|
||||
// En version web, cette fonctionnalité n'est pas disponible
|
||||
if (kIsWeb) {
|
||||
debugPrint('Ouverture des paramètres de l\'application non disponible en version web');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await Geolocator.openAppSettings();
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de l\'ouverture des paramètres de l\'application: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Ouvre les paramètres de localisation de l'appareil
|
||||
static Future<void> openLocationSettings() async {
|
||||
// En version web, cette fonctionnalité n'est pas disponible
|
||||
if (kIsWeb) {
|
||||
debugPrint('Ouverture des paramètres de localisation non disponible en version web');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await Geolocator.openLocationSettings();
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de l\'ouverture des paramètres de localisation: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
194
app/lib/core/services/passage_data_service.dart
Normal file
194
app/lib/core/services/passage_data_service.dart
Normal file
@@ -0,0 +1,194 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||
import 'package:geosector_app/core/repositories/passage_repository.dart';
|
||||
import 'package:geosector_app/core/repositories/user_repository.dart';
|
||||
|
||||
/// Service pour charger et filtrer les données de passages
|
||||
class PassageDataService {
|
||||
final PassageRepository passageRepository;
|
||||
final UserRepository userRepository;
|
||||
|
||||
PassageDataService({
|
||||
required this.passageRepository,
|
||||
required this.userRepository,
|
||||
});
|
||||
|
||||
/// Charge les données de passage depuis Hive
|
||||
///
|
||||
/// [daysToShow] : Nombre de jours à afficher
|
||||
/// [excludePassageTypes] : Types de passages à exclure
|
||||
/// [userId] : ID de l'utilisateur pour filtrer les passages (null = utilisateur actuel)
|
||||
/// [showAllPassages] : Si vrai, n'applique aucun filtrage par utilisateur
|
||||
List<Map<String, dynamic>> loadPassageData({
|
||||
required int daysToShow,
|
||||
List<int> excludePassageTypes = const [2],
|
||||
int? userId,
|
||||
bool showAllPassages = false,
|
||||
}) {
|
||||
// Récupérer tous les passages
|
||||
final passages = passageRepository.getAllPassages();
|
||||
|
||||
// Filtrer les passages pour exclure ceux avec fkType dans la liste d'exclusion
|
||||
final filteredPassages = passages
|
||||
.where((p) => !excludePassageTypes.contains(p.fkType))
|
||||
.toList();
|
||||
|
||||
if (filteredPassages.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Déterminer si on filtre par utilisateur ou si on prend tous les passages
|
||||
final passagesToUse = showAllPassages
|
||||
? filteredPassages
|
||||
: _filterPassagesByUser(filteredPassages, userId);
|
||||
|
||||
if (passagesToUse.isEmpty) {
|
||||
debugPrint('Aucun passage trouvé après filtrage');
|
||||
return [];
|
||||
}
|
||||
|
||||
// Trouver la date du passage le plus récent
|
||||
passagesToUse.sort((a, b) => b.passedAt.compareTo(a.passedAt));
|
||||
final DateTime referenceDate = passagesToUse.first.passedAt;
|
||||
debugPrint(
|
||||
'Date de référence pour le graphique: ${DateFormat('dd/MM/yyyy').format(referenceDate)}');
|
||||
|
||||
// Définir la date de début (N jours avant la date de référence)
|
||||
final startDate = referenceDate.subtract(Duration(days: daysToShow - 1));
|
||||
debugPrint(
|
||||
'Date de début pour le graphique: ${DateFormat('dd/MM/yyyy').format(startDate)}');
|
||||
debugPrint(
|
||||
'Plage de dates du graphique: ${DateFormat('dd/MM/yyyy').format(startDate)} - ${DateFormat('dd/MM/yyyy').format(referenceDate)}');
|
||||
|
||||
// Regrouper les passages par date et type
|
||||
final Map<String, Map<int, int>> passagesByDateAndType = {};
|
||||
|
||||
// Initialiser le dictionnaire avec les N derniers jours
|
||||
for (int i = daysToShow - 1; i >= 0; i--) {
|
||||
final date = referenceDate.subtract(Duration(days: i));
|
||||
final dateStr =
|
||||
'${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
||||
passagesByDateAndType[dateStr] = {};
|
||||
}
|
||||
|
||||
// Ajouter tous les types de passage possibles pour chaque date
|
||||
for (final dateStr in passagesByDateAndType.keys) {
|
||||
for (final typeId in AppKeys.typesPassages.keys) {
|
||||
// Exclure les types dans la liste d'exclusion
|
||||
if (!excludePassageTypes.contains(typeId)) {
|
||||
passagesByDateAndType[dateStr]![typeId] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parcourir les passages et les regrouper par date et type
|
||||
for (final passage in passagesToUse) {
|
||||
if (passage.passedAt
|
||||
.isAfter(startDate.subtract(const Duration(days: 1))) &&
|
||||
passage.passedAt
|
||||
.isBefore(referenceDate.add(const Duration(days: 1)))) {
|
||||
final dateStr =
|
||||
'${passage.passedAt.year}-${passage.passedAt.month.toString().padLeft(2, '0')}-${passage.passedAt.day.toString().padLeft(2, '0')}';
|
||||
final typeId = passage.fkType;
|
||||
|
||||
// Vérifier que le type n'est pas exclu
|
||||
if (!excludePassageTypes.contains(typeId)) {
|
||||
// Si la date existe dans notre dictionnaire, mettre à jour le compteur
|
||||
if (passagesByDateAndType.containsKey(dateStr)) {
|
||||
if (!passagesByDateAndType[dateStr]!.containsKey(typeId)) {
|
||||
passagesByDateAndType[dateStr]![typeId] = 0;
|
||||
}
|
||||
passagesByDateAndType[dateStr]![typeId] =
|
||||
(passagesByDateAndType[dateStr]![typeId] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convertir les données au format attendu par le graphique
|
||||
final List<Map<String, dynamic>> result = [];
|
||||
passagesByDateAndType.forEach((dateStr, typesCounts) {
|
||||
typesCounts.forEach((typeId, count) {
|
||||
result.add({
|
||||
'date': dateStr,
|
||||
'type_passage': typeId,
|
||||
'nb': count,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Filtre les passages par utilisateur
|
||||
List<dynamic> _filterPassagesByUser(List<dynamic> passages, int? userId) {
|
||||
// Récupérer l'ID de l'utilisateur actuel si nécessaire
|
||||
final int? currentUserId = userId ?? userRepository.getCurrentUser()?.id;
|
||||
|
||||
// Filtrer les passages pour l'utilisateur actuel
|
||||
final userPassages = passages
|
||||
.where((p) => currentUserId == null || p.fkUser == currentUserId)
|
||||
.toList();
|
||||
|
||||
if (userPassages.isEmpty) {
|
||||
debugPrint('Aucun passage trouvé pour l\'utilisateur $currentUserId');
|
||||
}
|
||||
|
||||
return userPassages;
|
||||
}
|
||||
|
||||
/// Charge et prépare les données pour le graphique en camembert
|
||||
///
|
||||
/// [excludePassageTypes] : Types de passages à exclure
|
||||
/// [userId] : ID de l'utilisateur pour filtrer les passages (null = utilisateur actuel)
|
||||
/// [showAllPassages] : Si vrai, n'applique aucun filtrage par utilisateur
|
||||
Map<int, int> loadPassageDataForPieChart({
|
||||
List<int> excludePassageTypes = const [2],
|
||||
int? userId,
|
||||
bool showAllPassages = false,
|
||||
}) {
|
||||
// Récupérer tous les passages
|
||||
final passages = passageRepository.getAllPassages();
|
||||
|
||||
// Filtrer les passages pour exclure ceux avec fkType dans la liste d'exclusion
|
||||
final filteredPassages = passages
|
||||
.where((p) => !excludePassageTypes.contains(p.fkType))
|
||||
.toList();
|
||||
|
||||
if (filteredPassages.isEmpty) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Déterminer si on filtre par utilisateur ou si on prend tous les passages
|
||||
final passagesToUse = showAllPassages
|
||||
? filteredPassages
|
||||
: _filterPassagesByUser(filteredPassages, userId);
|
||||
|
||||
if (passagesToUse.isEmpty) {
|
||||
debugPrint('Aucun passage trouvé après filtrage');
|
||||
return {};
|
||||
}
|
||||
|
||||
// Compter les passages par type
|
||||
final Map<int, int> passagesByType = {};
|
||||
|
||||
// Initialiser les compteurs pour tous les types de passage
|
||||
for (final typeId in AppKeys.typesPassages.keys) {
|
||||
// Exclure les types dans la liste d'exclusion
|
||||
if (!excludePassageTypes.contains(typeId)) {
|
||||
passagesByType[typeId] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Compter les passages par type
|
||||
for (final passage in passagesToUse) {
|
||||
final typeId = passage.fkType;
|
||||
if (!excludePassageTypes.contains(typeId)) {
|
||||
passagesByType[typeId] = (passagesByType[typeId] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return passagesByType;
|
||||
}
|
||||
}
|
||||
96
app/lib/core/services/sync_service.dart
Normal file
96
app/lib/core/services/sync_service.dart
Normal file
@@ -0,0 +1,96 @@
|
||||
import 'dart:async';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:geosector_app/core/repositories/user_repository.dart';
|
||||
|
||||
class SyncService {
|
||||
final UserRepository _userRepository;
|
||||
|
||||
StreamSubscription? _connectivitySubscription;
|
||||
Timer? _periodicSyncTimer;
|
||||
|
||||
bool _isSyncing = false;
|
||||
final Duration _syncInterval = const Duration(minutes: 15);
|
||||
|
||||
SyncService({
|
||||
required UserRepository userRepository,
|
||||
}) : _userRepository = userRepository {
|
||||
_initConnectivityListener();
|
||||
_initPeriodicSync();
|
||||
}
|
||||
|
||||
// Initialiser l'écouteur de connectivité
|
||||
void _initConnectivityListener() {
|
||||
_connectivitySubscription = Connectivity()
|
||||
.onConnectivityChanged
|
||||
.listen((List<ConnectivityResult> results) {
|
||||
// Vérifier si au moins un type de connexion est disponible
|
||||
if (results.any((result) => result != ConnectivityResult.none)) {
|
||||
// Lorsque la connexion est rétablie, déclencher une synchronisation
|
||||
syncAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialiser la synchronisation périodique
|
||||
void _initPeriodicSync() {
|
||||
_periodicSyncTimer = Timer.periodic(_syncInterval, (timer) {
|
||||
syncAll();
|
||||
});
|
||||
}
|
||||
|
||||
// Synchroniser toutes les données
|
||||
Future<void> syncAll() async {
|
||||
if (_isSyncing) return;
|
||||
|
||||
_isSyncing = true;
|
||||
|
||||
try {
|
||||
// Synchroniser les utilisateurs
|
||||
await _userRepository.syncAllUsers();
|
||||
} catch (e) {
|
||||
// Gérer les erreurs de synchronisation
|
||||
print('Erreur lors de la synchronisation: $e');
|
||||
} finally {
|
||||
_isSyncing = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Synchroniser uniquement les données d'un utilisateur spécifique
|
||||
Future<void> syncUserData(int userId) async {
|
||||
try {
|
||||
// Cette méthode pourrait être étendue à l'avenir pour synchroniser d'autres données utilisateur
|
||||
await _userRepository.refreshFromServer();
|
||||
} catch (e) {
|
||||
print('Erreur lors de la synchronisation des données utilisateur: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Forcer le rafraîchissement depuis le serveur
|
||||
Future<void> forceRefresh() async {
|
||||
if (_isSyncing) return;
|
||||
|
||||
_isSyncing = true;
|
||||
|
||||
try {
|
||||
// Rafraîchir depuis le serveur
|
||||
await _userRepository.refreshFromServer();
|
||||
} catch (e) {
|
||||
print('Erreur lors du rafraîchissement forcé: $e');
|
||||
} finally {
|
||||
_isSyncing = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Obtenir l'état de synchronisation
|
||||
Map<String, dynamic> getSyncStatus() {
|
||||
return {
|
||||
'isSyncing': _isSyncing,
|
||||
};
|
||||
}
|
||||
|
||||
// Nettoyer les ressources
|
||||
void dispose() {
|
||||
_connectivitySubscription?.cancel();
|
||||
_periodicSyncTimer?.cancel();
|
||||
}
|
||||
}
|
||||
190
app/lib/core/theme/app_theme.dart
Normal file
190
app/lib/core/theme/app_theme.dart
Normal 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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user