Appli Flutter se connecte bien à l'API.

This commit is contained in:
DahoudG
2025-09-12 03:15:21 +00:00
parent 8184bc77bb
commit 3df010add7
33 changed files with 3124 additions and 339 deletions

View File

@@ -3,7 +3,7 @@ import 'package:injectable/injectable.dart';
import 'package:jwt_decoder/jwt_decoder.dart';
import '../models/auth_state.dart';
import '../models/login_request.dart';
import '../models/login_response.dart';
import '../models/user_info.dart';
import '../storage/secure_token_storage.dart';
import 'auth_api_service.dart';

View File

@@ -1,5 +1,5 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import '../models/auth_state.dart';
import '../models/login_request.dart';
import '../models/user_info.dart';

View File

@@ -1,5 +1,5 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import '../models/auth_state.dart';
import '../models/login_request.dart';
import '../models/user_info.dart';

View File

@@ -83,7 +83,8 @@ class SecureTokenStorage {
/// Récupère la date d'expiration du refresh token
Future<DateTime?> getRefreshTokenExpirationDate() async {
try {
final expiresAtString = await _storage.read(key: _refreshExpiresAtKey);
final prefs = await _prefs;
final expiresAtString = prefs.getString(_refreshExpiresAtKey);
if (expiresAtString == null) return null;
return DateTime.parse(expiresAtString);
@@ -133,9 +134,10 @@ class SecureTokenStorage {
/// Met à jour le token d'accès
Future<void> updateAccessToken(String accessToken, DateTime expiresAt) async {
try {
final prefs = await _prefs;
await Future.wait([
_storage.write(key: _accessTokenKey, value: accessToken),
_storage.write(key: _expiresAtKey, value: expiresAt.toIso8601String()),
prefs.setString(_accessTokenKey, accessToken),
prefs.setString(_expiresAtKey, expiresAt.toIso8601String()),
]);
} catch (e) {
throw StorageException('Erreur lors de la mise à jour du token d\'accès: $e');
@@ -145,8 +147,9 @@ class SecureTokenStorage {
/// Vérifie si les données d'authentification existent
Future<bool> hasAuthData() async {
try {
final accessToken = await _storage.read(key: _accessTokenKey);
final refreshToken = await _storage.read(key: _refreshTokenKey);
final prefs = await _prefs;
final accessToken = prefs.getString(_accessTokenKey);
final refreshToken = prefs.getString(_refreshTokenKey);
return accessToken != null && refreshToken != null;
} catch (e) {
return false;
@@ -184,12 +187,13 @@ class SecureTokenStorage {
/// Efface toutes les données d'authentification
Future<void> clearAuthData() async {
try {
final prefs = await _prefs;
await Future.wait([
_storage.delete(key: _accessTokenKey),
_storage.delete(key: _refreshTokenKey),
_storage.delete(key: _userInfoKey),
_storage.delete(key: _expiresAtKey),
_storage.delete(key: _refreshExpiresAtKey),
prefs.remove(_accessTokenKey),
prefs.remove(_refreshTokenKey),
prefs.remove(_userInfoKey),
prefs.remove(_expiresAtKey),
prefs.remove(_refreshExpiresAtKey),
]);
} catch (e) {
throw StorageException('Erreur lors de l\'effacement des données d\'authentification: $e');
@@ -199,7 +203,8 @@ class SecureTokenStorage {
/// Active/désactive l'authentification biométrique
Future<void> setBiometricEnabled(bool enabled) async {
try {
await _storage.write(key: _biometricEnabledKey, value: enabled.toString());
final prefs = await _prefs;
await prefs.setBool(_biometricEnabledKey, enabled);
} catch (e) {
throw StorageException('Erreur lors de la configuration biométrique: $e');
}
@@ -208,8 +213,8 @@ class SecureTokenStorage {
/// Vérifie si l'authentification biométrique est activée
Future<bool> isBiometricEnabled() async {
try {
final enabled = await _storage.read(key: _biometricEnabledKey);
return enabled == 'true';
final prefs = await _prefs;
return prefs.getBool(_biometricEnabledKey) ?? false;
} catch (e) {
return false;
}
@@ -218,7 +223,8 @@ class SecureTokenStorage {
/// Efface toutes les données stockées
Future<void> clearAll() async {
try {
await _storage.deleteAll();
final prefs = await _prefs;
await prefs.clear();
} catch (e) {
throw StorageException('Erreur lors de l\'effacement de toutes les données: $e');
}
@@ -227,8 +233,8 @@ class SecureTokenStorage {
/// Vérifie si le stockage sécurisé est disponible
Future<bool> isAvailable() async {
try {
await _storage.containsKey(key: 'test');
return true;
final prefs = await _prefs;
return prefs.containsKey('test');
} catch (e) {
return false;
}

View File

@@ -1,7 +1,7 @@
class AppConstants {
// API Configuration
static const String baseUrl = 'http://localhost:8099'; // Backend UnionFlow
static const String apiVersion = '/api/v1';
static const String baseUrl = 'http://192.168.1.13:8080'; // Backend UnionFlow
static const String apiVersion = '/api';
// Timeout
static const Duration connectTimeout = Duration(seconds: 30);

View File

@@ -4,42 +4,60 @@
// InjectableConfigGenerator
// **************************************************************************
// ignore_for_file: unnecessary_lambdas
// ignore_for_file: lines_longer_than_80_chars
// ignore_for_file: type=lint
// coverage:ignore-file
import 'package:get_it/get_it.dart' as _i1;
import 'package:injectable/injectable.dart' as _i2;
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:get_it/get_it.dart' as _i174;
import 'package:injectable/injectable.dart' as _i526;
import 'package:unionflow_mobile_apps/core/auth/bloc/auth_bloc.dart' as _i635;
import 'package:unionflow_mobile_apps/core/auth/services/auth_api_service.dart'
as _i705;
import 'package:unionflow_mobile_apps/core/auth/services/auth_service.dart'
as _i423;
import 'package:unionflow_mobile_apps/core/auth/storage/secure_token_storage.dart'
as _i394;
import 'package:unionflow_mobile_apps/core/network/auth_interceptor.dart'
as _i772;
import 'package:unionflow_mobile_apps/core/network/dio_client.dart' as _i978;
import 'package:unionflow_mobile_apps/core/services/api_service.dart' as _i238;
import 'package:unionflow_mobile_apps/features/members/data/repositories/membre_repository_impl.dart'
as _i108;
import 'package:unionflow_mobile_apps/features/members/domain/repositories/membre_repository.dart'
as _i930;
import 'package:unionflow_mobile_apps/features/members/presentation/bloc/membres_bloc.dart'
as _i41;
import '../auth/bloc/auth_bloc.dart' as _i7;
import '../auth/services/auth_api_service.dart' as _i4;
import '../auth/services/auth_service.dart' as _i6;
import '../auth/storage/secure_token_storage.dart' as _i3;
import '../network/auth_interceptor.dart' as _i5;
import '../network/dio_client.dart' as _i8;
extension GetItInjectableX on _i1.GetIt {
// initializes the registration of main-scope dependencies inside of GetIt
Future<_i1.GetIt> init({
extension GetItInjectableX on _i174.GetIt {
// initializes the registration of main-scope dependencies inside of GetIt
_i174.GetIt init({
String? environment,
_i2.EnvironmentFilter? environmentFilter,
}) async {
final gh = _i2.GetItHelper(
_i526.EnvironmentFilter? environmentFilter,
}) {
final gh = _i526.GetItHelper(
this,
environment,
environmentFilter,
);
gh.singleton<_i3.SecureTokenStorage>(() => _i3.SecureTokenStorage());
gh.singleton<_i8.DioClient>(() => _i8.DioClient());
gh.singleton<_i5.AuthInterceptor>(() => _i5.AuthInterceptor(gh<_i3.SecureTokenStorage>()));
gh.singleton<_i4.AuthApiService>(() => _i4.AuthApiService(gh<_i8.DioClient>()));
gh.singleton<_i6.AuthService>(() => _i6.AuthService(
gh<_i3.SecureTokenStorage>(),
gh<_i4.AuthApiService>(),
gh<_i5.AuthInterceptor>(),
gh<_i8.DioClient>(),
));
gh.singleton<_i7.AuthBloc>(() => _i7.AuthBloc(gh<_i6.AuthService>()));
gh.singleton<_i394.SecureTokenStorage>(() => _i394.SecureTokenStorage());
gh.singleton<_i978.DioClient>(() => _i978.DioClient());
gh.singleton<_i705.AuthApiService>(
() => _i705.AuthApiService(gh<_i978.DioClient>()));
gh.singleton<_i238.ApiService>(
() => _i238.ApiService(gh<_i978.DioClient>()));
gh.singleton<_i772.AuthInterceptor>(
() => _i772.AuthInterceptor(gh<_i394.SecureTokenStorage>()));
gh.lazySingleton<_i930.MembreRepository>(
() => _i108.MembreRepositoryImpl(gh<_i238.ApiService>()));
gh.factory<_i41.MembresBloc>(
() => _i41.MembresBloc(gh<_i930.MembreRepository>()));
gh.singleton<_i423.AuthService>(() => _i423.AuthService(
gh<_i394.SecureTokenStorage>(),
gh<_i705.AuthApiService>(),
gh<_i772.AuthInterceptor>(),
gh<_i978.DioClient>(),
));
gh.singleton<_i635.AuthBloc>(() => _i635.AuthBloc(gh<_i423.AuthService>()));
return this;
}
}
}

View File

@@ -9,7 +9,7 @@ final GetIt getIt = GetIt.instance;
/// Configure l'injection de dépendances
@InjectableInit()
Future<void> configureDependencies() async {
await getIt.init();
getIt.init();
}
/// Réinitialise les dépendances (utile pour les tests)

View File

@@ -0,0 +1,186 @@
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
part 'membre_model.g.dart';
/// Modèle de données pour un membre UnionFlow
/// Aligné avec MembreDTO du serveur API
@JsonSerializable()
class MembreModel extends Equatable {
/// ID unique du membre
final String? id;
/// Numéro unique du membre (format: UF-YYYY-XXXXXXXX)
@JsonKey(name: 'numeroMembre')
final String numeroMembre;
/// Nom de famille du membre
final String nom;
/// Prénom du membre
final String prenom;
/// Adresse email
final String email;
/// Numéro de téléphone
final String telephone;
/// Date de naissance
@JsonKey(name: 'dateNaissance')
final DateTime? dateNaissance;
/// Adresse complète
final String? adresse;
/// Ville
final String? ville;
/// Code postal
@JsonKey(name: 'codePostal')
final String? codePostal;
/// Pays
final String? pays;
/// Profession
final String? profession;
/// Statut du membre (ACTIF, INACTIF, SUSPENDU)
final String statut;
/// Date d'adhésion
@JsonKey(name: 'dateAdhesion')
final DateTime dateAdhesion;
/// Date de création
@JsonKey(name: 'dateCreation')
final DateTime dateCreation;
/// Date de dernière modification
@JsonKey(name: 'dateModification')
final DateTime? dateModification;
/// Indique si le membre est actif
final bool actif;
/// Version pour optimistic locking
final int version;
const MembreModel({
this.id,
required this.numeroMembre,
required this.nom,
required this.prenom,
required this.email,
required this.telephone,
this.dateNaissance,
this.adresse,
this.ville,
this.codePostal,
this.pays,
this.profession,
required this.statut,
required this.dateAdhesion,
required this.dateCreation,
this.dateModification,
required this.actif,
required this.version,
});
/// Constructeur depuis JSON
factory MembreModel.fromJson(Map<String, dynamic> json) =>
_$MembreModelFromJson(json);
/// Conversion vers JSON
Map<String, dynamic> toJson() => _$MembreModelToJson(this);
/// Nom complet du membre
String get nomComplet => '$prenom $nom';
/// Initiales du membre
String get initiales {
final prenomInitial = prenom.isNotEmpty ? prenom[0].toUpperCase() : '';
final nomInitial = nom.isNotEmpty ? nom[0].toUpperCase() : '';
return '$prenomInitial$nomInitial';
}
/// Adresse complète formatée
String get adresseComplete {
final parts = <String>[];
if (adresse?.isNotEmpty == true) parts.add(adresse!);
if (ville?.isNotEmpty == true) parts.add(ville!);
if (codePostal?.isNotEmpty == true) parts.add(codePostal!);
if (pays?.isNotEmpty == true) parts.add(pays!);
return parts.join(', ');
}
/// Copie avec modifications
MembreModel copyWith({
String? id,
String? numeroMembre,
String? nom,
String? prenom,
String? email,
String? telephone,
DateTime? dateNaissance,
String? adresse,
String? ville,
String? codePostal,
String? pays,
String? profession,
String? statut,
DateTime? dateAdhesion,
DateTime? dateCreation,
DateTime? dateModification,
bool? actif,
int? version,
}) {
return MembreModel(
id: id ?? this.id,
numeroMembre: numeroMembre ?? this.numeroMembre,
nom: nom ?? this.nom,
prenom: prenom ?? this.prenom,
email: email ?? this.email,
telephone: telephone ?? this.telephone,
dateNaissance: dateNaissance ?? this.dateNaissance,
adresse: adresse ?? this.adresse,
ville: ville ?? this.ville,
codePostal: codePostal ?? this.codePostal,
pays: pays ?? this.pays,
profession: profession ?? this.profession,
statut: statut ?? this.statut,
dateAdhesion: dateAdhesion ?? this.dateAdhesion,
dateCreation: dateCreation ?? this.dateCreation,
dateModification: dateModification ?? this.dateModification,
actif: actif ?? this.actif,
version: version ?? this.version,
);
}
@override
List<Object?> get props => [
id,
numeroMembre,
nom,
prenom,
email,
telephone,
dateNaissance,
adresse,
ville,
codePostal,
pays,
profession,
statut,
dateAdhesion,
dateCreation,
dateModification,
actif,
version,
];
@override
String toString() => 'MembreModel(id: $id, numeroMembre: $numeroMembre, '
'nomComplet: $nomComplet, email: $email, statut: $statut)';
}

View File

@@ -0,0 +1,54 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'membre_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
MembreModel _$MembreModelFromJson(Map<String, dynamic> json) => MembreModel(
id: json['id'] as String?,
numeroMembre: json['numeroMembre'] as String,
nom: json['nom'] as String,
prenom: json['prenom'] as String,
email: json['email'] as String,
telephone: json['telephone'] as String,
dateNaissance: json['dateNaissance'] == null
? null
: DateTime.parse(json['dateNaissance'] as String),
adresse: json['adresse'] as String?,
ville: json['ville'] as String?,
codePostal: json['codePostal'] as String?,
pays: json['pays'] as String?,
profession: json['profession'] as String?,
statut: json['statut'] as String,
dateAdhesion: DateTime.parse(json['dateAdhesion'] as String),
dateCreation: DateTime.parse(json['dateCreation'] as String),
dateModification: json['dateModification'] == null
? null
: DateTime.parse(json['dateModification'] as String),
actif: json['actif'] as bool,
version: (json['version'] as num).toInt(),
);
Map<String, dynamic> _$MembreModelToJson(MembreModel instance) =>
<String, dynamic>{
'id': instance.id,
'numeroMembre': instance.numeroMembre,
'nom': instance.nom,
'prenom': instance.prenom,
'email': instance.email,
'telephone': instance.telephone,
'dateNaissance': instance.dateNaissance?.toIso8601String(),
'adresse': instance.adresse,
'ville': instance.ville,
'codePostal': instance.codePostal,
'pays': instance.pays,
'profession': instance.profession,
'statut': instance.statut,
'dateAdhesion': instance.dateAdhesion.toIso8601String(),
'dateCreation': instance.dateCreation.toIso8601String(),
'dateModification': instance.dateModification?.toIso8601String(),
'actif': instance.actif,
'version': instance.version,
};

View File

@@ -0,0 +1,206 @@
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
part 'wave_checkout_session_model.g.dart';
/// Modèle pour les sessions de paiement Wave Money
/// Aligné avec WaveCheckoutSessionDTO du serveur API
@JsonSerializable()
class WaveCheckoutSessionModel extends Equatable {
/// ID unique de la session
final String? id;
/// ID de la session Wave (retourné par l'API Wave)
@JsonKey(name: 'waveSessionId')
final String waveSessionId;
/// URL de la session de paiement Wave
@JsonKey(name: 'waveUrl')
final String? waveUrl;
/// Montant du paiement
final double montant;
/// Devise (XOF pour la Côte d'Ivoire)
final String devise;
/// URL de succès (redirection après paiement réussi)
@JsonKey(name: 'successUrl')
final String successUrl;
/// URL d'erreur (redirection après échec)
@JsonKey(name: 'errorUrl')
final String errorUrl;
/// Statut de la session
final String statut;
/// ID de l'organisation qui effectue le paiement
@JsonKey(name: 'organisationId')
final String? organisationId;
/// Nom de l'organisation
@JsonKey(name: 'nomOrganisation')
final String? nomOrganisation;
/// ID du membre qui effectue le paiement
@JsonKey(name: 'membreId')
final String? membreId;
/// Nom du membre
@JsonKey(name: 'nomMembre')
final String? nomMembre;
/// Type de paiement (COTISATION, ADHESION, AIDE, EVENEMENT)
@JsonKey(name: 'typePaiement')
final String? typePaiement;
/// Description du paiement
final String? description;
/// Référence externe
@JsonKey(name: 'referenceExterne')
final String? referenceExterne;
/// Date de création
@JsonKey(name: 'dateCreation')
final DateTime dateCreation;
/// Date d'expiration
@JsonKey(name: 'dateExpiration')
final DateTime? dateExpiration;
/// Date de dernière modification
@JsonKey(name: 'dateModification')
final DateTime? dateModification;
/// Indique si la session est active
final bool actif;
/// Version pour optimistic locking
final int version;
const WaveCheckoutSessionModel({
this.id,
required this.waveSessionId,
this.waveUrl,
required this.montant,
required this.devise,
required this.successUrl,
required this.errorUrl,
required this.statut,
this.organisationId,
this.nomOrganisation,
this.membreId,
this.nomMembre,
this.typePaiement,
this.description,
this.referenceExterne,
required this.dateCreation,
this.dateExpiration,
this.dateModification,
required this.actif,
required this.version,
});
/// Constructeur depuis JSON
factory WaveCheckoutSessionModel.fromJson(Map<String, dynamic> json) =>
_$WaveCheckoutSessionModelFromJson(json);
/// Conversion vers JSON
Map<String, dynamic> toJson() => _$WaveCheckoutSessionModelToJson(this);
/// Montant formaté avec devise
String get montantFormate => '${montant.toStringAsFixed(0)} $devise';
/// Indique si la session est expirée
bool get estExpiree {
if (dateExpiration == null) return false;
return DateTime.now().isAfter(dateExpiration!);
}
/// Indique si la session est en attente
bool get estEnAttente => statut == 'PENDING' || statut == 'EN_ATTENTE';
/// Indique si la session est réussie
bool get estReussie => statut == 'SUCCESS' || statut == 'REUSSIE';
/// Indique si la session a échoué
bool get aEchoue => statut == 'FAILED' || statut == 'ECHEC';
/// Copie avec modifications
WaveCheckoutSessionModel copyWith({
String? id,
String? waveSessionId,
String? waveUrl,
double? montant,
String? devise,
String? successUrl,
String? errorUrl,
String? statut,
String? organisationId,
String? nomOrganisation,
String? membreId,
String? nomMembre,
String? typePaiement,
String? description,
String? referenceExterne,
DateTime? dateCreation,
DateTime? dateExpiration,
DateTime? dateModification,
bool? actif,
int? version,
}) {
return WaveCheckoutSessionModel(
id: id ?? this.id,
waveSessionId: waveSessionId ?? this.waveSessionId,
waveUrl: waveUrl ?? this.waveUrl,
montant: montant ?? this.montant,
devise: devise ?? this.devise,
successUrl: successUrl ?? this.successUrl,
errorUrl: errorUrl ?? this.errorUrl,
statut: statut ?? this.statut,
organisationId: organisationId ?? this.organisationId,
nomOrganisation: nomOrganisation ?? this.nomOrganisation,
membreId: membreId ?? this.membreId,
nomMembre: nomMembre ?? this.nomMembre,
typePaiement: typePaiement ?? this.typePaiement,
description: description ?? this.description,
referenceExterne: referenceExterne ?? this.referenceExterne,
dateCreation: dateCreation ?? this.dateCreation,
dateExpiration: dateExpiration ?? this.dateExpiration,
dateModification: dateModification ?? this.dateModification,
actif: actif ?? this.actif,
version: version ?? this.version,
);
}
@override
List<Object?> get props => [
id,
waveSessionId,
waveUrl,
montant,
devise,
successUrl,
errorUrl,
statut,
organisationId,
nomOrganisation,
membreId,
nomMembre,
typePaiement,
description,
referenceExterne,
dateCreation,
dateExpiration,
dateModification,
actif,
version,
];
@override
String toString() => 'WaveCheckoutSessionModel(id: $id, '
'waveSessionId: $waveSessionId, montant: $montantFormate, '
'statut: $statut, typePaiement: $typePaiement)';
}

View File

@@ -0,0 +1,61 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'wave_checkout_session_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
WaveCheckoutSessionModel _$WaveCheckoutSessionModelFromJson(
Map<String, dynamic> json) =>
WaveCheckoutSessionModel(
id: json['id'] as String?,
waveSessionId: json['waveSessionId'] as String,
waveUrl: json['waveUrl'] as String?,
montant: (json['montant'] as num).toDouble(),
devise: json['devise'] as String,
successUrl: json['successUrl'] as String,
errorUrl: json['errorUrl'] as String,
statut: json['statut'] as String,
organisationId: json['organisationId'] as String?,
nomOrganisation: json['nomOrganisation'] as String?,
membreId: json['membreId'] as String?,
nomMembre: json['nomMembre'] as String?,
typePaiement: json['typePaiement'] as String?,
description: json['description'] as String?,
referenceExterne: json['referenceExterne'] as String?,
dateCreation: DateTime.parse(json['dateCreation'] as String),
dateExpiration: json['dateExpiration'] == null
? null
: DateTime.parse(json['dateExpiration'] as String),
dateModification: json['dateModification'] == null
? null
: DateTime.parse(json['dateModification'] as String),
actif: json['actif'] as bool,
version: (json['version'] as num).toInt(),
);
Map<String, dynamic> _$WaveCheckoutSessionModelToJson(
WaveCheckoutSessionModel instance) =>
<String, dynamic>{
'id': instance.id,
'waveSessionId': instance.waveSessionId,
'waveUrl': instance.waveUrl,
'montant': instance.montant,
'devise': instance.devise,
'successUrl': instance.successUrl,
'errorUrl': instance.errorUrl,
'statut': instance.statut,
'organisationId': instance.organisationId,
'nomOrganisation': instance.nomOrganisation,
'membreId': instance.membreId,
'nomMembre': instance.nomMembre,
'typePaiement': instance.typePaiement,
'description': instance.description,
'referenceExterne': instance.referenceExterne,
'dateCreation': instance.dateCreation.toIso8601String(),
'dateExpiration': instance.dateExpiration?.toIso8601String(),
'dateModification': instance.dateModification?.toIso8601String(),
'actif': instance.actif,
'version': instance.version,
};

View File

@@ -19,7 +19,7 @@ class DioClient {
void _configureOptions() {
_dio.options = BaseOptions(
// URL de base de l'API
baseUrl: 'http://localhost:8081', // Adresse de votre API Quarkus
baseUrl: 'http://192.168.1.13:8080', // Adresse de votre API Quarkus
// Timeouts
connectTimeout: const Duration(seconds: 30),

View File

@@ -0,0 +1,214 @@
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart';
import '../models/membre_model.dart';
import '../models/wave_checkout_session_model.dart';
import '../network/dio_client.dart';
/// Service API principal pour communiquer avec le serveur UnionFlow
@singleton
class ApiService {
final DioClient _dioClient;
ApiService(this._dioClient);
Dio get _dio => _dioClient.dio;
// ========================================
// MEMBRES
// ========================================
/// Récupère la liste de tous les membres actifs
Future<List<MembreModel>> getMembres() async {
try {
final response = await _dio.get('/api/membres');
if (response.data is List) {
return (response.data as List)
.map((json) => MembreModel.fromJson(json as Map<String, dynamic>))
.toList();
}
throw Exception('Format de réponse invalide pour la liste des membres');
} on DioException catch (e) {
throw _handleDioException(e, 'Erreur lors de la récupération des membres');
}
}
/// Récupère un membre par son ID
Future<MembreModel> getMembreById(String id) async {
try {
final response = await _dio.get('/api/membres/$id');
return MembreModel.fromJson(response.data as Map<String, dynamic>);
} on DioException catch (e) {
throw _handleDioException(e, 'Erreur lors de la récupération du membre');
}
}
/// Crée un nouveau membre
Future<MembreModel> createMembre(MembreModel membre) async {
try {
final response = await _dio.post(
'/api/membres',
data: membre.toJson(),
);
return MembreModel.fromJson(response.data as Map<String, dynamic>);
} on DioException catch (e) {
throw _handleDioException(e, 'Erreur lors de la création du membre');
}
}
/// Met à jour un membre existant
Future<MembreModel> updateMembre(String id, MembreModel membre) async {
try {
final response = await _dio.put(
'/api/membres/$id',
data: membre.toJson(),
);
return MembreModel.fromJson(response.data as Map<String, dynamic>);
} on DioException catch (e) {
throw _handleDioException(e, 'Erreur lors de la mise à jour du membre');
}
}
/// Désactive un membre
Future<void> deleteMembre(String id) async {
try {
await _dio.delete('/api/membres/$id');
} on DioException catch (e) {
throw _handleDioException(e, 'Erreur lors de la suppression du membre');
}
}
/// Recherche des membres par nom ou prénom
Future<List<MembreModel>> searchMembres(String query) async {
try {
final response = await _dio.get(
'/api/membres/recherche',
queryParameters: {'q': query},
);
if (response.data is List) {
return (response.data as List)
.map((json) => MembreModel.fromJson(json as Map<String, dynamic>))
.toList();
}
throw Exception('Format de réponse invalide pour la recherche');
} on DioException catch (e) {
throw _handleDioException(e, 'Erreur lors de la recherche de membres');
}
}
/// Récupère les statistiques des membres
Future<Map<String, dynamic>> getMembresStats() async {
try {
final response = await _dio.get('/api/membres/stats');
return response.data as Map<String, dynamic>;
} on DioException catch (e) {
throw _handleDioException(e, 'Erreur lors de la récupération des statistiques');
}
}
// ========================================
// PAIEMENTS WAVE
// ========================================
/// Crée une session de paiement Wave
Future<WaveCheckoutSessionModel> createWaveSession({
required double montant,
required String devise,
required String successUrl,
required String errorUrl,
String? organisationId,
String? membreId,
String? typePaiement,
String? description,
}) async {
try {
final response = await _dio.post(
'/api/paiements/wave/sessions',
data: {
'montant': montant,
'devise': devise,
'successUrl': successUrl,
'errorUrl': errorUrl,
if (organisationId != null) 'organisationId': organisationId,
if (membreId != null) 'membreId': membreId,
if (typePaiement != null) 'typePaiement': typePaiement,
if (description != null) 'description': description,
},
);
return WaveCheckoutSessionModel.fromJson(response.data as Map<String, dynamic>);
} on DioException catch (e) {
throw _handleDioException(e, 'Erreur lors de la création de la session Wave');
}
}
/// Récupère une session de paiement Wave par son ID
Future<WaveCheckoutSessionModel> getWaveSession(String sessionId) async {
try {
final response = await _dio.get('/api/paiements/wave/sessions/$sessionId');
return WaveCheckoutSessionModel.fromJson(response.data as Map<String, dynamic>);
} on DioException catch (e) {
throw _handleDioException(e, 'Erreur lors de la récupération de la session Wave');
}
}
/// Vérifie le statut d'une session de paiement Wave
Future<String> checkWaveSessionStatus(String sessionId) async {
try {
final response = await _dio.get('/api/paiements/wave/sessions/$sessionId/status');
return response.data['statut'] as String;
} on DioException catch (e) {
throw _handleDioException(e, 'Erreur lors de la vérification du statut Wave');
}
}
// ========================================
// GESTION DES ERREURS
// ========================================
/// Gère les exceptions Dio et les convertit en messages d'erreur appropriés
Exception _handleDioException(DioException e, String defaultMessage) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return Exception('Délai d\'attente dépassé. Vérifiez votre connexion internet.');
case DioExceptionType.badResponse:
final statusCode = e.response?.statusCode;
final responseData = e.response?.data;
if (statusCode == 400) {
if (responseData is Map && responseData.containsKey('message')) {
return Exception(responseData['message']);
}
return Exception('Données invalides');
} else if (statusCode == 401) {
return Exception('Non autorisé. Veuillez vous reconnecter.');
} else if (statusCode == 403) {
return Exception('Accès interdit');
} else if (statusCode == 404) {
return Exception('Ressource non trouvée');
} else if (statusCode == 500) {
return Exception('Erreur serveur. Veuillez réessayer plus tard.');
}
return Exception('$defaultMessage (Code: $statusCode)');
case DioExceptionType.cancel:
return Exception('Requête annulée');
case DioExceptionType.connectionError:
return Exception('Erreur de connexion. Vérifiez votre connexion internet.');
case DioExceptionType.badCertificate:
return Exception('Certificat SSL invalide');
case DioExceptionType.unknown:
default:
return Exception(defaultMessage);
}
}
}