feat: Add User and UserBalance models with serialization methods
feat: Implement BalanceRepository for group balance calculations feat: Create ExpenseRepository for managing expenses feat: Add services for handling expenses and storage operations fix: Update import paths for models in repositories and services refactor: Rename CountContent to AccountContent in HomePage chore: Add StorageService for image upload and management
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:travel_mate/services/error_service.dart';
|
||||
import '../data/models/account.dart';
|
||||
import '../models/account.dart';
|
||||
|
||||
class AccountService {
|
||||
final _errorService = ErrorService();
|
||||
|
||||
267
lib/services/balance_service.dart
Normal file
267
lib/services/balance_service.dart
Normal file
@@ -0,0 +1,267 @@
|
||||
import '../models/group_balance.dart';
|
||||
import '../models/expense.dart';
|
||||
import '../models/group_statistics.dart';
|
||||
import '../models/user_balance.dart';
|
||||
import '../models/settlement.dart';
|
||||
import '../repositories/balance_repository.dart';
|
||||
import '../repositories/expense_repository.dart';
|
||||
import 'error_service.dart';
|
||||
|
||||
class BalanceService {
|
||||
final BalanceRepository _balanceRepository;
|
||||
final ExpenseRepository _expenseRepository;
|
||||
final ErrorService _errorService;
|
||||
|
||||
BalanceService({
|
||||
required BalanceRepository balanceRepository,
|
||||
required ExpenseRepository expenseRepository,
|
||||
ErrorService? errorService,
|
||||
}) : _balanceRepository = balanceRepository,
|
||||
_expenseRepository = expenseRepository,
|
||||
_errorService = errorService ?? ErrorService();
|
||||
|
||||
/// Calculer la balance complète d'un groupe
|
||||
Future<GroupBalance> calculateGroupBalance(String groupId) async {
|
||||
try {
|
||||
return await _balanceRepository.calculateGroupBalance(groupId);
|
||||
} catch (e) {
|
||||
_errorService.logError('BalanceService', 'Erreur calcul balance groupe: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Stream de la balance en temps réel
|
||||
Stream<GroupBalance> getGroupBalanceStream(String groupId) {
|
||||
return _expenseRepository.getExpensesStream(groupId).asyncMap((expenses) async {
|
||||
try {
|
||||
final userBalances = calculateUserBalances(expenses);
|
||||
final settlements = optimizeSettlements(userBalances);
|
||||
final totalExpenses = expenses
|
||||
.where((e) => !e.isArchived)
|
||||
.fold(0.0, (sum, e) => sum + e.amountInEur);
|
||||
|
||||
return GroupBalance(
|
||||
groupId: groupId,
|
||||
userBalances: userBalances,
|
||||
settlements: settlements,
|
||||
totalExpenses: totalExpenses,
|
||||
calculatedAt: DateTime.now(),
|
||||
);
|
||||
} catch (e) {
|
||||
_errorService.logError('BalanceService', 'Erreur stream balance: $e');
|
||||
rethrow;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Calculer les balances individuelles (logique métier)
|
||||
List<UserBalance> calculateUserBalances(List<Expense> expenses) {
|
||||
final Map<String, _UserBalanceCalculator> calculators = {};
|
||||
|
||||
// Initialiser les calculateurs pour chaque utilisateur
|
||||
for (final expense in expenses) {
|
||||
if (expense.isArchived) continue;
|
||||
|
||||
// Ajouter le payeur
|
||||
calculators.putIfAbsent(
|
||||
expense.paidById,
|
||||
() => _UserBalanceCalculator(expense.paidById, expense.paidByName),
|
||||
);
|
||||
|
||||
// Ajouter les participants
|
||||
for (final split in expense.splits) {
|
||||
calculators.putIfAbsent(
|
||||
split.userId,
|
||||
() => _UserBalanceCalculator(split.userId, split.userName),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculer les montants
|
||||
for (final expense in expenses) {
|
||||
if (expense.isArchived) continue;
|
||||
|
||||
// Ajouter au montant payé
|
||||
calculators[expense.paidById]!.addPaid(expense.amountInEur);
|
||||
|
||||
// Ajouter aux montants dus
|
||||
for (final split in expense.splits) {
|
||||
calculators[split.userId]!.addOwed(split.amount);
|
||||
}
|
||||
}
|
||||
|
||||
// Convertir en UserBalance
|
||||
return calculators.values.map((calc) => calc.toUserBalance()).toList();
|
||||
}
|
||||
|
||||
/// Optimiser les règlements (algorithme avancé)
|
||||
List<Settlement> optimizeSettlements(List<UserBalance> balances) {
|
||||
final settlements = <Settlement>[];
|
||||
|
||||
// Filtrer les utilisateurs avec une balance significative (> 0.01€)
|
||||
final creditors = balances
|
||||
.where((b) => b.shouldReceive && b.balance > 0.01)
|
||||
.toList();
|
||||
final debtors = balances
|
||||
.where((b) => b.shouldPay && b.balance < -0.01)
|
||||
.toList();
|
||||
|
||||
if (creditors.isEmpty || debtors.isEmpty) {
|
||||
return settlements;
|
||||
}
|
||||
|
||||
// Trier par montant absolu décroissant pour optimiser
|
||||
creditors.sort((a, b) => b.balance.compareTo(a.balance));
|
||||
debtors.sort((a, b) => a.balance.compareTo(b.balance));
|
||||
|
||||
// Utiliser des copies mutables pour les calculs
|
||||
final creditorsRemaining = Map<String, double>.fromEntries(
|
||||
creditors.map((c) => MapEntry(c.userId, c.balance))
|
||||
);
|
||||
final debtorsRemaining = Map<String, double>.fromEntries(
|
||||
debtors.map((d) => MapEntry(d.userId, -d.balance))
|
||||
);
|
||||
|
||||
// Algorithme glouton optimisé
|
||||
for (final creditor in creditors) {
|
||||
final creditRemaining = creditorsRemaining[creditor.userId] ?? 0;
|
||||
if (creditRemaining <= 0.01) continue;
|
||||
|
||||
for (final debtor in debtors) {
|
||||
final debtRemaining = debtorsRemaining[debtor.userId] ?? 0;
|
||||
if (debtRemaining <= 0.01) continue;
|
||||
|
||||
final settlementAmount = _calculateOptimalSettlementAmount(
|
||||
creditRemaining,
|
||||
debtRemaining,
|
||||
);
|
||||
|
||||
if (settlementAmount > 0.01) {
|
||||
settlements.add(Settlement(
|
||||
fromUserId: debtor.userId,
|
||||
fromUserName: debtor.userName,
|
||||
toUserId: creditor.userId,
|
||||
toUserName: creditor.userName,
|
||||
amount: settlementAmount,
|
||||
));
|
||||
|
||||
// Mettre à jour les montants restants
|
||||
creditorsRemaining[creditor.userId] = creditRemaining - settlementAmount;
|
||||
debtorsRemaining[debtor.userId] = debtRemaining - settlementAmount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _validateSettlements(settlements);
|
||||
}
|
||||
|
||||
/// Calculer le montant optimal pour un règlement
|
||||
double _calculateOptimalSettlementAmount(double creditAmount, double debtAmount) {
|
||||
final amount = [creditAmount, debtAmount].reduce((a, b) => a < b ? a : b);
|
||||
// Arrondir à 2 décimales
|
||||
return (amount * 100).round() / 100;
|
||||
}
|
||||
|
||||
/// Valider les règlements calculés
|
||||
List<Settlement> _validateSettlements(List<Settlement> settlements) {
|
||||
// Supprimer les règlements trop petits
|
||||
final validSettlements = settlements
|
||||
.where((s) => s.amount > 0.01)
|
||||
.toList();
|
||||
|
||||
// Log pour debug en cas de problème
|
||||
final totalSettlements = validSettlements.fold(0.0, (sum, s) => sum + s.amount);
|
||||
_errorService.logInfo('BalanceService',
|
||||
'Règlements calculés: ${validSettlements.length}, Total: ${totalSettlements.toStringAsFixed(2)}€');
|
||||
|
||||
return validSettlements;
|
||||
}
|
||||
|
||||
/// Calculer la dette entre deux utilisateurs spécifiques
|
||||
double calculateDebtBetweenUsers(String groupId, String userId1, String userId2) {
|
||||
// Cette méthode pourrait être utile pour des fonctionnalités avancées
|
||||
// comme "Combien me doit X ?" ou "Combien je dois à Y ?"
|
||||
return 0.0; // TODO: Implémenter si nécessaire
|
||||
}
|
||||
|
||||
/// Analyser les tendances de dépenses par catégorie
|
||||
Map<String, double> analyzeCategorySpending(List<Expense> expenses) {
|
||||
final categoryTotals = <String, double>{};
|
||||
|
||||
for (final expense in expenses) {
|
||||
if (expense.isArchived) continue;
|
||||
|
||||
final categoryName = expense.category.displayName;
|
||||
categoryTotals[categoryName] = (categoryTotals[categoryName] ?? 0) + expense.amountInEur;
|
||||
}
|
||||
|
||||
return categoryTotals;
|
||||
}
|
||||
|
||||
/// Calculer les statistiques du groupe
|
||||
GroupStatistics calculateGroupStatistics(List<Expense> expenses) {
|
||||
if (expenses.isEmpty) {
|
||||
return GroupStatistics.empty();
|
||||
}
|
||||
|
||||
final nonArchivedExpenses = expenses.where((e) => !e.isArchived).toList();
|
||||
final totalAmount = nonArchivedExpenses.fold(0.0, (sum, e) => sum + e.amountInEur);
|
||||
final averageAmount = totalAmount / nonArchivedExpenses.length;
|
||||
|
||||
final categorySpending = analyzeCategorySpending(nonArchivedExpenses);
|
||||
final topCategory = categorySpending.entries
|
||||
.reduce((a, b) => a.value > b.value ? a : b)
|
||||
.key;
|
||||
|
||||
return GroupStatistics(
|
||||
totalExpenses: totalAmount,
|
||||
expenseCount: nonArchivedExpenses.length,
|
||||
averageExpense: averageAmount,
|
||||
topCategory: topCategory,
|
||||
categoryBreakdown: categorySpending,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> markSettlementAsCompleted({
|
||||
required String groupId,
|
||||
required String fromUserId,
|
||||
required String toUserId,
|
||||
required double amount,
|
||||
}) async {
|
||||
try {
|
||||
// Ici vous pourriez enregistrer le règlement en base
|
||||
// ou créer une transaction de règlement
|
||||
|
||||
// Pour l'instant, on pourrait juste recalculer
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
_errorService.logSuccess('BalanceService', 'Règlement marqué comme effectué');
|
||||
} catch (e) {
|
||||
_errorService.logError('BalanceService', 'Erreur mark settlement: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Classe helper pour calculer les balances
|
||||
class _UserBalanceCalculator {
|
||||
final String userId;
|
||||
final String userName;
|
||||
double _totalPaid = 0.0;
|
||||
double _totalOwed = 0.0;
|
||||
|
||||
_UserBalanceCalculator(this.userId, this.userName);
|
||||
|
||||
void addPaid(double amount) => _totalPaid += amount;
|
||||
void addOwed(double amount) => _totalOwed += amount;
|
||||
|
||||
UserBalance toUserBalance() {
|
||||
return UserBalance(
|
||||
userId: userId,
|
||||
userName: userName,
|
||||
totalPaid: _totalPaid,
|
||||
totalOwed: _totalOwed,
|
||||
balance: _totalPaid - _totalOwed,
|
||||
);
|
||||
}
|
||||
}
|
||||
91
lib/services/expense_service.dart
Normal file
91
lib/services/expense_service.dart
Normal file
@@ -0,0 +1,91 @@
|
||||
import 'dart:io';
|
||||
import '../models/expense.dart';
|
||||
import '../repositories/expense_repository.dart';
|
||||
import 'error_service.dart';
|
||||
import 'storage_service.dart'; // Pour upload des reçus
|
||||
|
||||
class ExpenseService {
|
||||
final ExpenseRepository _expenseRepository;
|
||||
final ErrorService _errorService;
|
||||
final StorageService _storageService;
|
||||
|
||||
ExpenseService({
|
||||
required ExpenseRepository expenseRepository,
|
||||
ErrorService? errorService,
|
||||
StorageService? storageService,
|
||||
}) : _expenseRepository = expenseRepository,
|
||||
_errorService = errorService ?? ErrorService(),
|
||||
_storageService = storageService ?? StorageService();
|
||||
|
||||
// Création avec validation et upload d'image
|
||||
Future<String> createExpenseWithValidation(Expense expense, File? receiptImage) async {
|
||||
try {
|
||||
// Validation métier
|
||||
_validateExpenseData(expense);
|
||||
|
||||
// Upload du reçu si présent
|
||||
String? receiptUrl;
|
||||
if (receiptImage != null) {
|
||||
receiptUrl = await _storageService.uploadReceiptImage(
|
||||
expense.groupId,
|
||||
receiptImage,
|
||||
);
|
||||
}
|
||||
|
||||
// Créer l'expense avec l'URL du reçu
|
||||
final expenseWithReceipt = expense.copyWith(receiptUrl: receiptUrl);
|
||||
|
||||
return await _expenseRepository.createExpense(expenseWithReceipt);
|
||||
} catch (e) {
|
||||
_errorService.logError('ExpenseService', 'Erreur création expense: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// Mise à jour avec validation
|
||||
Future<void> updateExpenseWithValidation(Expense expense, File? newReceiptImage) async {
|
||||
try {
|
||||
_validateExpenseData(expense);
|
||||
|
||||
// Gérer le nouvel upload si nécessaire
|
||||
String? receiptUrl = expense.receiptUrl;
|
||||
if (newReceiptImage != null) {
|
||||
receiptUrl = await _storageService.uploadReceiptImage(
|
||||
expense.groupId,
|
||||
newReceiptImage,
|
||||
);
|
||||
}
|
||||
|
||||
final expenseWithReceipt = expense.copyWith(
|
||||
receiptUrl: receiptUrl,
|
||||
editedAt: DateTime.now(),
|
||||
isEdited: true,
|
||||
);
|
||||
|
||||
await _expenseRepository.updateExpense(expenseWithReceipt);
|
||||
} catch (e) {
|
||||
_errorService.logError('ExpenseService', 'Erreur update expense: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// Validation des données
|
||||
void _validateExpenseData(Expense expense) {
|
||||
if (expense.description.trim().isEmpty) {
|
||||
throw Exception('La description est requise');
|
||||
}
|
||||
|
||||
if (expense.amount <= 0) {
|
||||
throw Exception('Le montant doit être positif');
|
||||
}
|
||||
|
||||
if (expense.splits.isEmpty) {
|
||||
throw Exception('Au moins un participant est requis');
|
||||
}
|
||||
|
||||
final totalSplits = expense.splits.fold(0.0, (sum, split) => sum + split.amount);
|
||||
if ((totalSplits - expense.amountInEur).abs() > 0.01) {
|
||||
throw Exception('La somme des répartitions ne correspond pas au montant total');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:travel_mate/data/models/group.dart';
|
||||
import 'package:travel_mate/models/group.dart';
|
||||
import 'package:travel_mate/services/error_service.dart';
|
||||
|
||||
class GroupService {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import '../data/models/message.dart';
|
||||
import '../models/message.dart';
|
||||
import '../repositories/message_repository.dart';
|
||||
import 'error_service.dart';
|
||||
|
||||
|
||||
214
lib/services/storage_service.dart
Normal file
214
lib/services/storage_service.dart
Normal file
@@ -0,0 +1,214 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:firebase_storage/firebase_storage.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'error_service.dart';
|
||||
|
||||
class StorageService {
|
||||
final FirebaseStorage _storage;
|
||||
final ErrorService _errorService;
|
||||
|
||||
StorageService({
|
||||
FirebaseStorage? storage,
|
||||
ErrorService? errorService,
|
||||
}) : _storage = storage ?? FirebaseStorage.instance,
|
||||
_errorService = errorService ?? ErrorService();
|
||||
|
||||
/// Upload d'une image de reçu pour une dépense
|
||||
Future<String> uploadReceiptImage(String groupId, File imageFile) async {
|
||||
try {
|
||||
// Validation du fichier
|
||||
_validateImageFile(imageFile);
|
||||
|
||||
// Compression de l'image
|
||||
final compressedImage = await _compressImage(imageFile);
|
||||
|
||||
// Génération du nom de fichier unique
|
||||
final fileName = _generateReceiptFileName(groupId);
|
||||
|
||||
// Référence vers le storage
|
||||
final storageRef = _storage.ref().child('receipts/$groupId/$fileName');
|
||||
|
||||
// Métadonnées pour optimiser le cache et la compression
|
||||
final metadata = SettableMetadata(
|
||||
contentType: 'image/jpeg',
|
||||
customMetadata: {
|
||||
'groupId': groupId,
|
||||
'uploadedAt': DateTime.now().toIso8601String(),
|
||||
'compressed': 'true',
|
||||
},
|
||||
);
|
||||
|
||||
// Upload du fichier
|
||||
final uploadTask = storageRef.putData(compressedImage, metadata);
|
||||
|
||||
// Monitoring du progrès (optionnel)
|
||||
uploadTask.snapshotEvents.listen((TaskSnapshot snapshot) {
|
||||
final progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
|
||||
_errorService.logInfo('StorageService', 'Upload progress: ${progress.toStringAsFixed(1)}%');
|
||||
});
|
||||
|
||||
// Attendre la completion
|
||||
final snapshot = await uploadTask;
|
||||
|
||||
// Récupérer l'URL de téléchargement
|
||||
final downloadUrl = await snapshot.ref.getDownloadURL();
|
||||
|
||||
_errorService.logSuccess('StorageService', 'Image uploadée avec succès: $fileName');
|
||||
return downloadUrl;
|
||||
|
||||
} catch (e) {
|
||||
_errorService.logError('StorageService', 'Erreur upload image: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Supprimer une image de reçu
|
||||
Future<void> deleteReceiptImage(String imageUrl) async {
|
||||
try {
|
||||
if (imageUrl.isEmpty) return;
|
||||
|
||||
// Extraire la référence depuis l'URL
|
||||
final ref = _storage.refFromURL(imageUrl);
|
||||
await ref.delete();
|
||||
|
||||
_errorService.logSuccess('StorageService', 'Image supprimée avec succès');
|
||||
} catch (e) {
|
||||
_errorService.logError('StorageService', 'Erreur suppression image: $e');
|
||||
// Ne pas rethrow pour éviter de bloquer la suppression de la dépense
|
||||
}
|
||||
}
|
||||
|
||||
/// Compresser une image pour optimiser le stockage
|
||||
Future<Uint8List> _compressImage(File imageFile) async {
|
||||
try {
|
||||
// Lire l'image
|
||||
final bytes = await imageFile.readAsBytes();
|
||||
img.Image? image = img.decodeImage(bytes);
|
||||
|
||||
if (image == null) {
|
||||
throw Exception('Impossible de décoder l\'image');
|
||||
}
|
||||
|
||||
// Redimensionner si l'image est trop grande
|
||||
const maxWidth = 1024;
|
||||
const maxHeight = 1024;
|
||||
|
||||
if (image.width > maxWidth || image.height > maxHeight) {
|
||||
image = img.copyResize(
|
||||
image,
|
||||
width: image.width > image.height ? maxWidth : null,
|
||||
height: image.height > image.width ? maxHeight : null,
|
||||
interpolation: img.Interpolation.linear,
|
||||
);
|
||||
}
|
||||
|
||||
// Encoder en JPEG avec compression
|
||||
final compressedBytes = img.encodeJpg(image, quality: 85);
|
||||
|
||||
_errorService.logInfo('StorageService',
|
||||
'Image compressée: ${bytes.length} → ${compressedBytes.length} bytes');
|
||||
|
||||
return Uint8List.fromList(compressedBytes);
|
||||
} catch (e) {
|
||||
_errorService.logError('StorageService', 'Erreur compression image: $e');
|
||||
// Fallback: retourner l'image originale si la compression échoue
|
||||
return await imageFile.readAsBytes();
|
||||
}
|
||||
}
|
||||
|
||||
/// Valider le fichier image
|
||||
void _validateImageFile(File imageFile) {
|
||||
// Vérifier que le fichier existe
|
||||
if (!imageFile.existsSync()) {
|
||||
throw Exception('Le fichier image n\'existe pas');
|
||||
}
|
||||
|
||||
// Vérifier la taille du fichier (max 10MB)
|
||||
const maxSizeBytes = 10 * 1024 * 1024; // 10MB
|
||||
final fileSize = imageFile.lengthSync();
|
||||
if (fileSize > maxSizeBytes) {
|
||||
throw Exception('La taille du fichier dépasse 10MB');
|
||||
}
|
||||
|
||||
// Vérifier l'extension
|
||||
final extension = path.extension(imageFile.path).toLowerCase();
|
||||
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.webp'];
|
||||
if (!allowedExtensions.contains(extension)) {
|
||||
throw Exception('Format d\'image non supporté. Utilisez JPG, PNG ou WebP');
|
||||
}
|
||||
}
|
||||
|
||||
/// Générer un nom de fichier unique pour un reçu
|
||||
String _generateReceiptFileName(String groupId) {
|
||||
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
final random = DateTime.now().microsecond;
|
||||
return 'receipt_${groupId}_${timestamp}_$random.jpg';
|
||||
}
|
||||
|
||||
/// Upload multiple d'images (pour futures fonctionnalités)
|
||||
Future<List<String>> uploadMultipleImages(
|
||||
String groupId,
|
||||
List<File> imageFiles,
|
||||
) async {
|
||||
final uploadTasks = imageFiles.map((file) => uploadReceiptImage(groupId, file));
|
||||
return await Future.wait(uploadTasks);
|
||||
}
|
||||
|
||||
/// Récupérer les métadonnées d'une image
|
||||
Future<FullMetadata?> getImageMetadata(String imageUrl) async {
|
||||
try {
|
||||
final ref = _storage.refFromURL(imageUrl);
|
||||
return await ref.getMetadata();
|
||||
} catch (e) {
|
||||
_errorService.logError('StorageService', 'Erreur récupération metadata: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Nettoyer les images orphelines d'un groupe
|
||||
Future<void> cleanupGroupImages(String groupId) async {
|
||||
try {
|
||||
final groupRef = _storage.ref().child('receipts/$groupId');
|
||||
final listResult = await groupRef.listAll();
|
||||
|
||||
for (final ref in listResult.items) {
|
||||
// Vérifier l'âge du fichier
|
||||
final metadata = await ref.getMetadata();
|
||||
final uploadDate = metadata.timeCreated;
|
||||
|
||||
if (uploadDate != null) {
|
||||
final daysSinceUpload = DateTime.now().difference(uploadDate).inDays;
|
||||
|
||||
// Supprimer les fichiers de plus de 30 jours sans dépense associée
|
||||
if (daysSinceUpload > 30) {
|
||||
await ref.delete();
|
||||
_errorService.logInfo('StorageService', 'Image orpheline supprimée: ${ref.name}');
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
_errorService.logError('StorageService', 'Erreur nettoyage images: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculer la taille totale utilisée par un groupe
|
||||
Future<int> getGroupStorageSize(String groupId) async {
|
||||
try {
|
||||
final groupRef = _storage.ref().child('receipts/$groupId');
|
||||
final listResult = await groupRef.listAll();
|
||||
|
||||
int totalSize = 0;
|
||||
for (final ref in listResult.items) {
|
||||
final metadata = await ref.getMetadata();
|
||||
totalSize += metadata.size ?? 0;
|
||||
}
|
||||
|
||||
return totalSize;
|
||||
} catch (e) {
|
||||
_errorService.logError('StorageService', 'Erreur calcul taille storage: $e');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:travel_mate/services/error_service.dart';
|
||||
import '../data/models/trip.dart';
|
||||
import '../models/trip.dart';
|
||||
|
||||
class TripService {
|
||||
final _errorService = ErrorService();
|
||||
|
||||
Reference in New Issue
Block a user