Initial commit du projet Flutter

This commit is contained in:
2025-12-01 10:56:37 +01:00
commit 8a728a612e
162 changed files with 33799 additions and 0 deletions

View File

@@ -0,0 +1,600 @@
// lib/services/connectivity_service.dart - VERSION AVEC SUPPORT ENTREPRISE
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import '../main.dart'; // Pour accéder à AuthController
class ConnectivityService extends ChangeNotifier {
static final ConnectivityService _instance = ConnectivityService._internal();
factory ConnectivityService() => _instance;
ConnectivityService._internal();
bool _isConnected = true;
bool _isChecking = false;
Timer? _periodicTimer;
bool _hasNetworkInterface = true;
DateTime? _lastDisconnectionTime;
int _reconnectionAttempts = 0;
bool get isConnected => _isConnected;
bool get hasNetworkInterface => _hasNetworkInterface;
DateTime? get lastDisconnectionTime => _lastDisconnectionTime;
int get reconnectionAttempts => _reconnectionAttempts;
// Clé globale pour accéder au navigator
static GlobalKey<NavigatorState>? navigatorKey;
/// Initialiser le service avec la clé navigator
static void setNavigatorKey(GlobalKey<NavigatorState> key) {
navigatorKey = key;
print('✅ NavigatorKey configuré pour ConnectivityService');
}
/// Démarrer la surveillance
void startMonitoring() {
print('🌐 Démarrage de la surveillance de connectivité');
_periodicTimer?.cancel();
_periodicTimer = Timer.periodic(Duration(seconds: 5), (timer) {
checkConnectivity();
});
// Vérification initiale après un délai
Future.delayed(Duration(seconds: 2), () {
checkConnectivity();
});
}
/// Vérifier la connectivité
Future<void> checkConnectivity() async {
if (_isChecking) return;
_isChecking = true;
try {
print('🔍 Vérification de la connectivité...');
// Étape 1: Vérifier les interfaces réseau
_hasNetworkInterface = await _checkNetworkInterfaces();
if (!_hasNetworkInterface) {
print('❌ Aucune interface réseau trouvée');
_updateConnectivityStatus(false);
return;
}
// Étape 2: Tester la connectivité internet
bool hasInternetAccess = await _testInternetConnectivity();
print('🌍 Accès internet: $hasInternetAccess');
_updateConnectivityStatus(hasInternetAccess);
} catch (e) {
print('❌ Erreur lors de la vérification: $e');
_updateConnectivityStatus(false);
} finally {
_isChecking = false;
}
}
/// Vérifier les interfaces réseau
Future<bool> _checkNetworkInterfaces() async {
try {
final interfaces = await NetworkInterface.list(
includeLinkLocal: false,
type: InternetAddressType.any,
);
bool hasActiveInterface = interfaces.any(
(interface) =>
!interface.name.toLowerCase().contains('lo') &&
interface.addresses.isNotEmpty,
);
print('📡 Interfaces réseau actives: $hasActiveInterface');
if (hasActiveInterface) {
print(
' Interfaces trouvées: ${interfaces.map((i) => i.name).join(", ")}',
);
}
return hasActiveInterface;
} catch (e) {
print('❌ Erreur vérification interfaces: $e');
return false;
}
}
/// Tester la connectivité internet avec plusieurs cibles
Future<bool> _testInternetConnectivity() async {
final testTargets = [
{'host': '1.1.1.1', 'port': 53, 'name': 'Cloudflare DNS'},
{'host': '8.8.8.8', 'port': 53, 'name': 'Google DNS'},
{'host': 'google.com', 'port': 80, 'name': 'Google'},
];
for (var target in testTargets) {
try {
final socket = await Socket.connect(
target['host'] as String,
target['port'] as int,
timeout: Duration(seconds: 3),
);
socket.destroy();
print('✅ Connexion réussie à ${target['name']}');
return true;
} catch (e) {
print('⚠️ Échec connexion à ${target['name']}: $e');
}
}
print('❌ Toutes les tentatives de connexion ont échoué');
return false;
}
/// Mettre à jour le statut de connectivité
void _updateConnectivityStatus(bool isConnected) {
if (isConnected != _isConnected) {
print('🔄 Changement connectivité: $_isConnected -> $isConnected');
final previousStatus = _isConnected;
_isConnected = isConnected;
if (!_isConnected) {
_lastDisconnectionTime = DateTime.now();
_reconnectionAttempts = 0;
_showNoConnectionDialog();
} else if (previousStatus == false && _isConnected) {
// Reconnexion réussie
print('✅ Reconnexion établie');
_reconnectionAttempts = 0;
_showReconnectionSuccess();
}
notifyListeners();
}
}
/// Afficher le dialog de perte de connexion
void _showNoConnectionDialog() {
if (navigatorKey?.currentState == null) {
print('⚠️ NavigatorState non disponible');
return;
}
print('🚨 Affichage du popup de perte de connexion');
navigatorKey!.currentState!.push(
PageRouteBuilder(
opaque: false,
barrierDismissible: false,
barrierColor: Colors.black54,
pageBuilder: (BuildContext context, _, __) {
return WillPopScope(
onWillPop: () async => false,
child: Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: _buildDialogContent(context),
),
);
},
),
);
}
/// Construire le contenu du dialog de déconnexion
Widget _buildDialogContent(BuildContext context) {
return Container(
padding: EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Icône animée
TweenAnimationBuilder<double>(
duration: Duration(milliseconds: 800),
tween: Tween(begin: 0.0, end: 1.0),
builder: (context, value, child) {
return Transform.scale(
scale: value,
child: Container(
width: 80,
height: 80,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.red[400]!, Colors.red[600]!],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.red.withOpacity(0.3),
blurRadius: 20,
offset: Offset(0, 10),
),
],
),
child: Icon(
Icons.wifi_off_rounded,
color: Colors.white,
size: 40,
),
),
);
},
),
SizedBox(height: 24),
// Titre
Text(
'Problème de connexion',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Color(0xFF006699),
),
textAlign: TextAlign.center,
),
SizedBox(height: 16),
// Message détaillé
Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.red[50],
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.red[200]!, width: 1),
),
child: Row(
children: [
Icon(
_hasNetworkInterface
? Icons.cloud_off_rounded
: Icons.wifi_off_rounded,
color: Colors.red[700],
size: 24,
),
SizedBox(width: 12),
Expanded(
child: Text(
_getDetailedMessage(),
style: TextStyle(
fontSize: 14,
color: Colors.red[900],
height: 1.5,
),
),
),
],
),
),
SizedBox(height: 24),
// Informations utilisateur (si connecté)
Consumer<AuthController>(
builder: (context, authController, child) {
if (authController.isLoggedIn) {
final isEnterprise = authController.isEnterpriseMember;
return Container(
padding: EdgeInsets.all(12),
margin: EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color:
isEnterprise
? Color(0xFF8B5CF6).withOpacity(0.1)
: Color(0xFF006699).withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color:
isEnterprise
? Color(0xFF8B5CF6).withOpacity(0.3)
: Color(0xFF006699).withOpacity(0.3),
),
),
child: Row(
children: [
Icon(
isEnterprise ? Icons.business : Icons.person,
color:
isEnterprise
? Color(0xFF8B5CF6)
: Color(0xFF006699),
size: 20,
),
SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
authController.agentName ?? 'Agent',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
overflow: TextOverflow.ellipsis,
),
if (isEnterprise &&
authController.enterprise?.nomEntreprise !=
null) ...[
SizedBox(height: 2),
Text(
authController.enterprise!.nomEntreprise,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
overflow: TextOverflow.ellipsis,
),
],
],
),
),
Container(
padding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color:
isEnterprise
? Color(0xFF8B5CF6)
: Color(0xFF006699),
borderRadius: BorderRadius.circular(8),
),
child: Text(
isEnterprise ? 'Entreprise' : 'Agent',
style: TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.w600,
),
),
),
],
),
);
}
return SizedBox.shrink();
},
),
// Boutons d'action
Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: () {
Navigator.of(context).pop();
_reconnectionAttempts++;
checkConnectivity();
},
icon: Icon(Icons.refresh_rounded, size: 20),
label: Text('Réessayer'),
style: OutlinedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 16),
side: BorderSide(color: Color(0xFF006699), width: 2),
foregroundColor: Color(0xFF006699),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
onPressed: () {
Navigator.of(context).pop();
_openWifiSettings();
},
icon: Icon(Icons.wifi_find_rounded, size: 20),
label: Text('WiFi'),
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF006699),
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 16),
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
],
),
// Compteur de tentatives
if (_reconnectionAttempts > 0) ...[
SizedBox(height: 12),
Text(
'Tentative ${_reconnectionAttempts + 1}',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
fontStyle: FontStyle.italic,
),
),
],
],
),
);
}
/// Message détaillé selon le type de problème
String _getDetailedMessage() {
if (!_hasNetworkInterface) {
return 'Aucun réseau WiFi détecté. Veuillez vous connecter à un réseau WiFi pour utiliser WORTIS Agent.';
} else {
return 'Connexion internet indisponible. Vérifiez que votre réseau WiFi a accès à internet.';
}
}
/// Afficher un message de reconnexion réussie
void _showReconnectionSuccess() {
if (navigatorKey?.currentContext == null) return;
final context = navigatorKey!.currentContext!;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
shape: BoxShape.circle,
),
child: Icon(Icons.wifi_rounded, color: Colors.white, size: 20),
),
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Connexion rétablie',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 2),
Text(
'Vous pouvez continuer à utiliser l\'application',
style: TextStyle(
color: Colors.white.withOpacity(0.9),
fontSize: 12,
),
),
],
),
),
],
),
backgroundColor: Colors.green[600],
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
duration: Duration(seconds: 3),
margin: EdgeInsets.all(16),
),
);
}
/// Ouvrir les paramètres WiFi
void _openWifiSettings() async {
try {
print('📱 Ouverture des paramètres WiFi');
const platform = MethodChannel('com.wortis.agent/settings');
// Sortir du mode kiosque si activé
try {
await platform.invokeMethod('exitKioskMode');
} catch (e) {
print('⚠️ Mode kiosque non actif ou erreur: $e');
}
// Ouvrir les paramètres WiFi
await platform.invokeMethod('openWifiSettings');
// Fermer le dialog
if (navigatorKey?.currentContext != null) {
Navigator.of(navigatorKey!.currentContext!).pop();
}
} catch (e) {
print('❌ Erreur ouverture paramètres WiFi: $e');
if (navigatorKey?.currentContext != null) {
ScaffoldMessenger.of(navigatorKey!.currentContext!).showSnackBar(
SnackBar(
content: Text('Impossible d\'ouvrir les paramètres WiFi'),
backgroundColor: Colors.red,
),
);
}
}
}
/// Forcer un test manuel (pour debug)
void forceCheck() {
print('🔧 Test forcé de connectivité');
checkConnectivity();
}
/// Méthode de test pour forcer l'affichage du popup
void showTestPopup() {
print('🧪 Test d\'affichage du popup de déconnexion');
_isConnected = false;
_lastDisconnectionTime = DateTime.now();
_reconnectionAttempts = 0;
_showNoConnectionDialog();
}
/// Simuler une reconnexion (pour test)
void simulateReconnection() {
print('🧪 Simulation de reconnexion');
_updateConnectivityStatus(true);
}
/// Obtenir les statistiques de connectivité
Map<String, dynamic> getConnectivityStats() {
return {
'is_connected': _isConnected,
'has_network_interface': _hasNetworkInterface,
'last_disconnection': _lastDisconnectionTime?.toIso8601String(),
'reconnection_attempts': _reconnectionAttempts,
'is_checking': _isChecking,
};
}
/// Réinitialiser les statistiques
void resetStats() {
_reconnectionAttempts = 0;
_lastDisconnectionTime = null;
print('📊 Statistiques de connectivité réinitialisées');
}
@override
void dispose() {
print('🛑 Arrêt du service de connectivité');
_periodicTimer?.cancel();
super.dispose();
}
}
/// Extension pour des méthodes utilitaires
extension ConnectivityServiceExtension on ConnectivityService {
/// Vérifier si la connexion est stable
bool get isStable {
return _isConnected && _reconnectionAttempts == 0;
}
/// Obtenir le temps depuis la dernière déconnexion
Duration? get timeSinceLastDisconnection {
if (_lastDisconnectionTime == null) return null;
return DateTime.now().difference(_lastDisconnectionTime!);
}
/// Obtenir un message de statut lisible
String get statusMessage {
if (_isConnected) {
if (_reconnectionAttempts > 0) {
return 'Connexion rétablie après $_reconnectionAttempts tentatives';
}
return 'Connecté';
} else {
if (!_hasNetworkInterface) {
return 'Aucun réseau WiFi';
}
return 'Pas de connexion internet';
}
}
}

View File

@@ -0,0 +1,561 @@
// Enhanced lib/services/wortis_api_service.dart
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import '../models/service_model.dart';
class WortisApiService {
static const String baseUrl = 'https://api.live.wortis.cg/tpe';
// Singleton pattern
static final WortisApiService _instance = WortisApiService._internal();
factory WortisApiService() => _instance;
WortisApiService._internal();
// Retry configuration
static const int maxRetries = 3;
static const Duration retryDelay = Duration(seconds: 2);
Future<List<WortisService>> getServices() async {
return _retryRequest<List<WortisService>>(() async {
final response = await http
.get(
Uri.parse('$baseUrl/get_services_back'),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Connection': 'keep-alive',
},
)
.timeout(Duration(seconds: 30));
if (response.statusCode == 200) {
final Map<String, dynamic> jsonData = json.decode(response.body);
final List<dynamic> servicesJson = jsonData['all_services'] ?? [];
return servicesJson
.map((serviceJson) => WortisService.fromJson(serviceJson))
.toList();
} else {
throw Exception('Erreur API: ${response.statusCode}');
}
});
}
// Méthode pour grouper les services par secteur
Map<String, List<WortisService>> groupServicesBySector(
List<WortisService> services,
) {
Map<String, List<WortisService>> grouped = {};
for (var service in services) {
String sector = service.secteurActivite;
if (grouped[sector] == null) {
grouped[sector] = [];
}
grouped[sector]!.add(service);
}
return grouped;
}
// Generic retry logic
Future<T> _retryRequest<T>(Future<T> Function() request) async {
Exception? lastException;
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await request();
} on SocketException catch (e) {
lastException = e;
print('Tentative $attempt échouée (SocketException): $e');
if (attempt < maxRetries) {
await Future.delayed(retryDelay * attempt);
}
} on HttpException catch (e) {
lastException = e;
print('Tentative $attempt échouée (HttpException): $e');
if (attempt < maxRetries) {
await Future.delayed(retryDelay * attempt);
}
} on http.ClientException catch (e) {
lastException = e;
print('Tentative $attempt échouée (ClientException): $e');
if (attempt < maxRetries) {
await Future.delayed(retryDelay * attempt);
}
} catch (e) {
// For other exceptions, don't retry
rethrow;
}
}
throw Exception(
'Impossible de se connecter après $maxRetries tentatives: $lastException',
);
}
}
class AuthApiService {
static const String baseUrl = 'https://api.live.wortis.cg/tpe';
static const int maxRetries = 3;
static const Duration retryDelay = Duration(seconds: 2);
/// Test de connexion à l'API avec retry
static Future<bool> testConnection() async {
try {
return await _retryRequest<bool>(() async {
final response = await http
.get(
Uri.parse('http://google.com/'),
headers: {
'Content-Type': 'application/json',
'Connection': 'keep-alive',
},
)
.timeout(Duration(seconds: 10));
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
return data['success'] == true;
}
return false;
});
} catch (e) {
print('Erreur test connexion après plusieurs tentatives: $e');
return false;
}
}
/// Connexion utilisateur avec retry
static Future<Map<String, dynamic>> login(String agentId, String pin) async {
try {
return await _retryRequest<Map<String, dynamic>>(() async {
final response = await http
.post(
Uri.parse('$baseUrl/auth/login'),
headers: {
'Content-Type': 'application/json',
'Connection': 'keep-alive',
},
body: jsonEncode({'agent_id': agentId, 'pin': pin}),
)
.timeout(Duration(seconds: 15));
return jsonDecode(response.body);
});
} catch (e) {
print('Erreur login API après retry: $e');
return {'success': false, 'message': 'Erreur de connexion au serveur'};
}
}
/// Créer un utilisateur avec retry
static Future<Map<String, dynamic>> createUser(
String nom,
String pin, {
String role = 'agent',
}) async {
try {
return await _retryRequest<Map<String, dynamic>>(() async {
final response = await http
.post(
Uri.parse('$baseUrl/users/register'),
headers: {
'Content-Type': 'application/json',
'Connection': 'keep-alive',
},
body: jsonEncode({'nom': nom, 'pin': pin, 'role': role}),
)
.timeout(Duration(seconds: 15));
return jsonDecode(response.body);
});
} catch (e) {
print('Erreur création utilisateur après retry: $e');
return {'success': false, 'message': 'Erreur de création'};
}
}
/// Récupérer le solde d'un agent avec retry
static Future<Map<String, dynamic>> getAgentBalance(String agentId) async {
try {
return await _retryRequest<Map<String, dynamic>>(() async {
final response = await http
.get(
Uri.parse('$baseUrl/agent/$agentId/balance'),
headers: {
'Content-Type': 'application/json',
'Connection': 'keep-alive',
},
)
.timeout(Duration(seconds: 15));
return jsonDecode(response.body);
});
} catch (e) {
print('Erreur récupération solde après retry: $e');
return {
'success': false,
'message': 'Erreur lors de la récupération du solde',
};
}
}
/// Recharger le solde d'un agent avec retry
static Future<Map<String, dynamic>> rechargeAgent(
String agentId,
double montant,
) async {
try {
return await _retryRequest<Map<String, dynamic>>(() async {
final response = await http
.post(
Uri.parse('$baseUrl/agent/$agentId/recharge'),
headers: {
'Content-Type': 'application/json',
'Connection': 'keep-alive',
},
body: jsonEncode({'montant': montant}),
)
.timeout(Duration(seconds: 15));
return jsonDecode(response.body);
});
} catch (e) {
print('Erreur recharge après retry: $e');
return {'success': false, 'message': 'Erreur lors de la recharge'};
}
}
/// Mettre à jour le solde d'un agent avec retry
static Future<Map<String, dynamic>> updateAgentBalance(
String agentId,
double nouveauSolde,
) async {
try {
return await _retryRequest<Map<String, dynamic>>(() async {
final response = await http
.put(
Uri.parse('$baseUrl/agent/$agentId/balance'),
headers: {
'Content-Type': 'application/json',
'Connection': 'keep-alive',
},
body: jsonEncode({'solde': nouveauSolde}),
)
.timeout(Duration(seconds: 15));
return jsonDecode(response.body);
});
} catch (e) {
print('Erreur mise à jour solde après retry: $e');
return {
'success': false,
'message': 'Erreur lors de la mise à jour du solde',
};
}
}
/// Enregistrer une transaction avec retry
static Future<Map<String, dynamic>> recordTransaction({
required String agentId,
required String serviceId,
required String serviceName,
required double montant,
required double commission,
required String typeTransaction,
required Map<String, dynamic> detailsTransaction,
}) async {
try {
return await _retryRequest<Map<String, dynamic>>(() async {
final response = await http
.post(
Uri.parse('$baseUrl/transactions'),
headers: {
'Content-Type': 'application/json',
'Connection': 'keep-alive',
},
body: jsonEncode({
'agent_id': agentId,
'service_id': serviceId,
'service_name': serviceName,
'montant': montant,
'commission': commission,
'type_transaction': typeTransaction,
'details': detailsTransaction,
'date_transaction': DateTime.now().toIso8601String(),
}),
)
.timeout(Duration(seconds: 15));
return jsonDecode(response.body);
});
} catch (e) {
print('Erreur enregistrement transaction après retry: $e');
return {
'success': false,
'message': 'Erreur lors de l\'enregistrement de la transaction',
};
}
}
// Generic retry logic for AuthApiService
static Future<T> _retryRequest<T>(Future<T> Function() request) async {
Exception? lastException;
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await request();
} on SocketException catch (e) {
lastException = e;
print('AuthAPI - Tentative $attempt échouée (SocketException): $e');
if (attempt < maxRetries) {
await Future.delayed(retryDelay * attempt);
}
} on HttpException catch (e) {
lastException = e;
print('AuthAPI - Tentative $attempt échouée (HttpException): $e');
if (attempt < maxRetries) {
await Future.delayed(retryDelay * attempt);
}
} on http.ClientException catch (e) {
lastException = e;
print('AuthAPI - Tentative $attempt échouée (ClientException): $e');
if (attempt < maxRetries) {
await Future.delayed(retryDelay * attempt);
}
} catch (e) {
// For other exceptions, don't retry
rethrow;
}
}
throw Exception(
'Impossible de se connecter après $maxRetries tentatives: $lastException',
);
}
}
class ApiService {
static const String baseUrl = 'https://api.live.wortis.cg/tpe';
static const int maxRetries = 3;
static const Duration retryDelay = Duration(seconds: 2);
/// Récupérer les champs d'un service avec retry amélioré
Future<Map<String, dynamic>> fetchServiceFields(String serviceName) async {
return _retryRequest<Map<String, dynamic>>(() async {
// Fix: Remove double slash in URL
final url = '$baseUrl/service/${Uri.encodeComponent(serviceName)}';
print('Fetching service fields from: $url');
final response = await http
.get(
Uri.parse(url),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Connection': 'keep-alive',
'User-Agent': 'WortisApp/1.0',
},
)
.timeout(Duration(seconds: 30));
print('Response status: ${response.statusCode}');
print('Response headers: ${response.headers}');
if (response.statusCode == 200) {
return jsonDecode(response.body);
} else {
throw Exception(
'Erreur API: ${response.statusCode} - ${response.body}',
);
}
});
}
/// Vérifier les données avec GET et retry
Future<Map<String, dynamic>> verifyDataGet(
String url,
Map<String, dynamic> params,
) async {
return _retryRequest<Map<String, dynamic>>(() async {
final uri = Uri.parse(url).replace(
queryParameters: params.map(
(key, value) => MapEntry(key, value.toString()),
),
);
final response = await http
.get(
uri,
headers: {
'Content-Type': 'application/json',
'Connection': 'keep-alive',
},
)
.timeout(Duration(seconds: 30));
if (response.statusCode == 200) {
return jsonDecode(response.body);
} else {
throw Exception(
'Données non trouvées - Status: ${response.statusCode}',
);
}
});
}
/// Vérifier les données avec POST et retry
Future<Map<String, dynamic>> verifyDataPost(
String url,
Map<String, dynamic> data,
String operationId,
) async {
return _retryRequest<Map<String, dynamic>>(() async {
final response = await http
.post(
Uri.parse(url),
headers: {
'Content-Type': 'application/json',
'Connection': 'keep-alive',
},
body: jsonEncode(data),
)
.timeout(Duration(seconds: 30));
if (response.statusCode == 200) {
return jsonDecode(response.body);
} else {
throw Exception(
'Données non trouvées - Status: ${response.statusCode}',
);
}
});
}
/// Soumettre les données du formulaire avec retry
Future<void> submitFormData(
BuildContext context,
String url,
Map<String, dynamic> data,
Map<String, dynamic>? serviceData,
dynamic additionalData,
bool isCardPayment,
) async {
try {
await _retryRequest<void>(() async {
print(url);
final response = await http
.post(
Uri.parse(url),
headers: {
'Content-Type': 'application/json',
'Connection': 'keep-alive',
},
body: jsonEncode(data),
)
.timeout(Duration(seconds: 30));
if (response.statusCode == 200) {
final responseData = jsonDecode(response.body);
// Afficher un message de succès
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
Icon(Icons.check_circle, color: Colors.white),
SizedBox(width: 8),
Text('Transaction effectuée avec succès'),
],
),
backgroundColor: Colors.green,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
);
// Retourner à la page précédente
Navigator.of(context).pop();
}
} else {
throw Exception(
'Erreur lors de la soumission - Status: ${response.statusCode}',
);
}
});
} catch (e) {
print('Erreur submitFormData après retry: $e');
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
Icon(Icons.error, color: Colors.white),
SizedBox(width: 8),
Expanded(
child: Text(
'Erreur lors de la transaction: ${e.toString()}',
maxLines: 2,
),
),
],
),
backgroundColor: Colors.red,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
duration: Duration(seconds: 5),
),
);
}
}
}
/// Annuler l'opération en cours
void cancelOperation() {
print('Opération annulée');
}
// Generic retry logic for ApiService
Future<T> _retryRequest<T>(Future<T> Function() request) async {
Exception? lastException;
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await request();
} on SocketException catch (e) {
lastException = e;
print('API - Tentative $attempt échouée (SocketException): $e');
if (attempt < maxRetries) {
await Future.delayed(retryDelay * attempt);
}
} on HttpException catch (e) {
lastException = e;
print('API - Tentative $attempt échouée (HttpException): $e');
if (attempt < maxRetries) {
await Future.delayed(retryDelay * attempt);
}
} on http.ClientException catch (e) {
lastException = e;
print('API - Tentative $attempt échouée (ClientException): $e');
if (attempt < maxRetries) {
await Future.delayed(retryDelay * attempt);
}
} catch (e) {
// For other exceptions, don't retry
rethrow;
}
}
throw Exception(
'Impossible de se connecter après $maxRetries tentatives: $lastException',
);
}
}