feat: Livraison version 3.0.6
- Amélioration de la gestion des entités et des utilisateurs - Mise à jour des modèles Amicale et Client avec champs supplémentaires - Ajout du service de logging et amélioration du chargement UI - Refactoring des formulaires utilisateur et amicale - Intégration de file_picker et image_picker pour la gestion des fichiers - Amélioration de la gestion des membres et de leur suppression - Optimisation des performances de l'API - Mise à jour de la documentation technique 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,10 @@ class ApiService {
|
||||
late final String _baseUrl;
|
||||
late final String _appIdentifier;
|
||||
String? _sessionId;
|
||||
|
||||
// Getters pour les propriétés (lecture seule)
|
||||
String? get sessionId => _sessionId;
|
||||
String get baseUrl => _baseUrl;
|
||||
|
||||
// Singleton thread-safe
|
||||
static ApiService get instance {
|
||||
@@ -142,8 +146,11 @@ class ApiService {
|
||||
Future<Response> post(String path, {dynamic data}) async {
|
||||
try {
|
||||
return await _dio.post(path, data: data);
|
||||
} on DioException catch (e) {
|
||||
throw ApiException.fromDioException(e);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
if (e is ApiException) rethrow;
|
||||
throw ApiException('Erreur inattendue lors de la requête POST', originalError: e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,8 +158,11 @@ class ApiService {
|
||||
Future<Response> get(String path, {Map<String, dynamic>? queryParameters}) async {
|
||||
try {
|
||||
return await _dio.get(path, queryParameters: queryParameters);
|
||||
} on DioException catch (e) {
|
||||
throw ApiException.fromDioException(e);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
if (e is ApiException) rethrow;
|
||||
throw ApiException('Erreur inattendue lors de la requête GET', originalError: e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,8 +170,11 @@ class ApiService {
|
||||
Future<Response> put(String path, {dynamic data}) async {
|
||||
try {
|
||||
return await _dio.put(path, data: data);
|
||||
} on DioException catch (e) {
|
||||
throw ApiException.fromDioException(e);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
if (e is ApiException) rethrow;
|
||||
throw ApiException('Erreur inattendue lors de la requête PUT', originalError: e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,8 +182,81 @@ class ApiService {
|
||||
Future<Response> delete(String path) async {
|
||||
try {
|
||||
return await _dio.delete(path);
|
||||
} on DioException catch (e) {
|
||||
throw ApiException.fromDioException(e);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
if (e is ApiException) rethrow;
|
||||
throw ApiException('Erreur inattendue lors de la requête DELETE', originalError: e);
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode pour uploader un logo d'amicale
|
||||
Future<Map<String, dynamic>> uploadLogo(int entiteId, dynamic imageFile) async {
|
||||
try {
|
||||
FormData formData;
|
||||
|
||||
// Gestion différente selon la plateforme (Web ou Mobile)
|
||||
if (kIsWeb) {
|
||||
// Pour le web, imageFile est un XFile
|
||||
final bytes = await imageFile.readAsBytes();
|
||||
|
||||
// Vérification de la taille (5 Mo max)
|
||||
const int maxSize = 5 * 1024 * 1024;
|
||||
if (bytes.length > maxSize) {
|
||||
throw ApiException(
|
||||
'Le fichier est trop volumineux. Taille maximale: 5 Mo',
|
||||
statusCode: 413
|
||||
);
|
||||
}
|
||||
|
||||
formData = FormData.fromMap({
|
||||
'logo': MultipartFile.fromBytes(
|
||||
bytes,
|
||||
filename: imageFile.name,
|
||||
),
|
||||
});
|
||||
} else {
|
||||
// Pour mobile, imageFile est un File
|
||||
final fileLength = await imageFile.length();
|
||||
|
||||
// Vérification de la taille (5 Mo max)
|
||||
const int maxSize = 5 * 1024 * 1024;
|
||||
if (fileLength > maxSize) {
|
||||
throw ApiException(
|
||||
'Le fichier est trop volumineux. Taille maximale: 5 Mo',
|
||||
statusCode: 413
|
||||
);
|
||||
}
|
||||
|
||||
formData = FormData.fromMap({
|
||||
'logo': await MultipartFile.fromFile(
|
||||
imageFile.path,
|
||||
filename: imageFile.path.split('/').last,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
final response = await _dio.post(
|
||||
'/entites/$entiteId/logo',
|
||||
data: formData,
|
||||
options: Options(
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
return response.data;
|
||||
} else {
|
||||
throw ApiException('Erreur lors de l\'upload du logo',
|
||||
statusCode: response.statusCode);
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
throw ApiException.fromDioException(e);
|
||||
} catch (e) {
|
||||
if (e is ApiException) rethrow;
|
||||
throw ApiException('Erreur inattendue lors de l\'upload du logo', originalError: e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
163
app/lib/core/services/logger_service.dart
Normal file
163
app/lib/core/services/logger_service.dart
Normal file
@@ -0,0 +1,163 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:geosector_app/core/services/api_service.dart';
|
||||
|
||||
/// Service de logging centralisé qui désactive automatiquement les logs en production
|
||||
class LoggerService {
|
||||
static LoggerService? _instance;
|
||||
static bool? _isProduction;
|
||||
|
||||
// Singleton
|
||||
static LoggerService get instance {
|
||||
_instance ??= LoggerService._internal();
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
LoggerService._internal();
|
||||
|
||||
/// Détermine si on est en production
|
||||
static bool get isProduction {
|
||||
if (_isProduction != null) return _isProduction!;
|
||||
|
||||
try {
|
||||
// Vérifier si ApiService est initialisé
|
||||
final env = ApiService.instance.getCurrentEnvironment();
|
||||
_isProduction = env == 'PROD';
|
||||
} catch (e) {
|
||||
// Si ApiService n'est pas encore initialisé, utiliser kReleaseMode
|
||||
_isProduction = kReleaseMode;
|
||||
}
|
||||
|
||||
return _isProduction!;
|
||||
}
|
||||
|
||||
/// Réinitialiser le cache de l'environnement (utile pour les tests)
|
||||
static void resetEnvironmentCache() {
|
||||
_isProduction = null;
|
||||
}
|
||||
|
||||
/// Log simple (remplace debugPrint)
|
||||
static void log(String message, {String? prefix}) {
|
||||
if (!isProduction) {
|
||||
if (prefix != null) {
|
||||
debugPrint('$prefix $message');
|
||||
} else {
|
||||
debugPrint(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Log d'information
|
||||
static void info(String message) {
|
||||
if (!isProduction) {
|
||||
debugPrint('ℹ️ $message');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log de succès
|
||||
static void success(String message) {
|
||||
if (!isProduction) {
|
||||
debugPrint('✅ $message');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log d'avertissement
|
||||
static void warning(String message) {
|
||||
if (!isProduction) {
|
||||
debugPrint('⚠️ $message');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log d'erreur (toujours affiché même en production pour le debugging)
|
||||
static void error(String message, [dynamic error, StackTrace? stackTrace]) {
|
||||
// Les erreurs sont toujours loggées, même en production
|
||||
debugPrint('❌ $message');
|
||||
if (error != null) {
|
||||
debugPrint(' Error: $error');
|
||||
}
|
||||
if (stackTrace != null && !isProduction) {
|
||||
// La stack trace n'est affichée qu'en DEV/REC
|
||||
debugPrint(' Stack trace:\n$stackTrace');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log de debug (avec emoji personnalisé)
|
||||
static void debug(String message, {String emoji = '🔧'}) {
|
||||
if (!isProduction) {
|
||||
debugPrint('$emoji $message');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log de requête API
|
||||
static void api(String message) {
|
||||
if (!isProduction) {
|
||||
debugPrint('🔗 $message');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log de base de données
|
||||
static void database(String message) {
|
||||
if (!isProduction) {
|
||||
debugPrint('💾 $message');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log de navigation
|
||||
static void navigation(String message) {
|
||||
if (!isProduction) {
|
||||
debugPrint('🧭 $message');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log de performance/timing
|
||||
static void performance(String message) {
|
||||
if (!isProduction) {
|
||||
debugPrint('⏱️ $message');
|
||||
}
|
||||
}
|
||||
|
||||
/// Log conditionnel basé sur un flag custom
|
||||
static void conditional(String message, {required bool condition}) {
|
||||
if (!isProduction && condition) {
|
||||
debugPrint(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Groupe de logs (pour regrouper visuellement des logs liés)
|
||||
static void group(String title, List<String> messages) {
|
||||
if (!isProduction) {
|
||||
debugPrint('┌─ $title');
|
||||
for (int i = 0; i < messages.length; i++) {
|
||||
if (i == messages.length - 1) {
|
||||
debugPrint('└─ ${messages[i]}');
|
||||
} else {
|
||||
debugPrint('├─ ${messages[i]}');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Log d'objet JSON (formaté)
|
||||
static void json(String label, Map<String, dynamic> data) {
|
||||
if (!isProduction) {
|
||||
debugPrint('📋 $label:');
|
||||
data.forEach((key, value) {
|
||||
debugPrint(' $key: $value');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension pour faciliter l'utilisation
|
||||
extension LoggerExtension on String {
|
||||
void log({String? prefix}) => LoggerService.log(this, prefix: prefix);
|
||||
void logInfo() => LoggerService.info(this);
|
||||
void logSuccess() => LoggerService.success(this);
|
||||
void logWarning() => LoggerService.warning(this);
|
||||
void logError([dynamic error, StackTrace? stackTrace]) =>
|
||||
LoggerService.error(this, error, stackTrace);
|
||||
void logDebug({String emoji = '🔧'}) => LoggerService.debug(this, emoji: emoji);
|
||||
void logApi() => LoggerService.api(this);
|
||||
void logDatabase() => LoggerService.database(this);
|
||||
void logNavigation() => LoggerService.navigation(this);
|
||||
void logPerformance() => LoggerService.performance(this);
|
||||
}
|
||||
Reference in New Issue
Block a user