Enhance model and service documentation with detailed comments and descriptions

- Updated Group, Trip, User, and other model classes to include comprehensive documentation for better understanding and maintainability.
- Improved error handling and logging in services, including AuthService, ErrorService, and StorageService.
- Added validation and business logic explanations in ExpenseService and TripService.
- Refactored method comments to follow a consistent format across the codebase.
- Translated error messages and comments from French to English for consistency.
This commit is contained in:
Dayron
2025-10-30 15:56:17 +01:00
parent 1eeea6997e
commit 2faf37f145
46 changed files with 2656 additions and 220 deletions

View File

@@ -1,3 +1,29 @@
/// A service that handles file storage operations using Firebase Storage.
///
/// This service provides functionality for:
/// - Receipt image upload and compression
/// - Profile image management
/// - File validation and optimization
/// - Automatic image compression to reduce storage costs
/// - Metadata management for uploaded files
///
/// The service automatically compresses images to JPEG format with 85% quality
/// to balance file size and image quality. It also generates unique filenames
/// and handles error logging through the ErrorService.
///
/// Example usage:
/// ```dart
/// final storageService = StorageService();
///
/// // Upload a receipt image
/// final receiptUrl = await storageService.uploadReceiptImage(groupId, imageFile);
///
/// // Upload a profile image
/// final profileUrl = await storageService.uploadProfileImage(userId, imageFile);
///
/// // Delete a file
/// await storageService.deleteFile(fileUrl);
/// ```
import 'dart:io';
import 'dart:typed_data';
import 'package:firebase_storage/firebase_storage.dart';
@@ -5,32 +31,55 @@ import 'package:path/path.dart' as path;
import 'package:image/image.dart' as img;
import 'error_service.dart';
/// Service for managing file storage operations with Firebase Storage.
class StorageService {
/// Firebase Storage instance for file operations
final FirebaseStorage _storage;
/// Service for error handling and logging
final ErrorService _errorService;
/// Constructor for StorageService.
///
/// Args:
/// [storage]: Optional Firebase Storage instance (auto-created if null)
/// [errorService]: Optional error service instance (auto-created if null)
StorageService({
FirebaseStorage? storage,
ErrorService? errorService,
}) : _storage = storage ?? FirebaseStorage.instance,
_errorService = errorService ?? ErrorService();
/// Upload d'une image de reçu pour une dépense
/// Uploads a receipt image for an expense with automatic compression.
///
/// Validates the image file, compresses it to JPEG format with 85% quality,
/// generates a unique filename, and uploads it with appropriate metadata.
/// Monitors upload progress and logs it for debugging purposes.
///
/// Args:
/// [groupId]: ID of the group this receipt belongs to
/// [imageFile]: The image file to upload
///
/// Returns:
/// A Future<String> containing the download URL of the uploaded image
///
/// Throws:
/// Exception if file validation fails or upload encounters an error
Future<String> uploadReceiptImage(String groupId, File imageFile) async {
try {
// Validation du fichier
// File validation
_validateImageFile(imageFile);
// Compression de l'image
// Image compression
final compressedImage = await _compressImage(imageFile);
// Génération du nom de fichier unique
// Generate unique filename
final fileName = _generateReceiptFileName(groupId);
// Référence vers le storage
// Storage reference
final storageRef = _storage.ref().child('receipts/$groupId/$fileName');
// Métadonnées pour optimiser le cache et la compression
// Metadata for cache optimization and compression info
final metadata = SettableMetadata(
contentType: 'image/jpeg',
customMetadata: {
@@ -40,58 +89,77 @@ class StorageService {
},
);
// Upload du fichier
// File upload
final uploadTask = storageRef.putData(compressedImage, metadata);
// Monitoring du progrès (optionnel)
// Progress monitoring (optional)
uploadTask.snapshotEvents.listen((TaskSnapshot snapshot) {
final progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
_errorService.logInfo('StorageService', 'Upload progress: ${progress.toStringAsFixed(1)}%');
});
// Attendre la completion
// Wait for completion
final snapshot = await uploadTask;
// Récupérer l'URL de téléchargement
// Get download URL
final downloadUrl = await snapshot.ref.getDownloadURL();
_errorService.logSuccess('StorageService', 'Image uploadée avec succès: $fileName');
_errorService.logSuccess('StorageService', 'Image uploaded successfully: $fileName');
return downloadUrl;
} catch (e) {
_errorService.logError('StorageService', 'Erreur upload image: $e');
_errorService.logError('StorageService', 'Error uploading image: $e');
rethrow;
}
}
/// Supprimer une image de reçu
/// Deletes a receipt image from storage.
///
/// Extracts the storage reference from the provided URL and deletes the file.
/// Does not throw errors to avoid blocking expense deletion operations.
///
/// Args:
/// [imageUrl]: The download URL of the image to delete
Future<void> deleteReceiptImage(String imageUrl) async {
try {
if (imageUrl.isEmpty) return;
// Extraire la référence depuis l'URL
// Extract reference from URL
final ref = _storage.refFromURL(imageUrl);
await ref.delete();
_errorService.logSuccess('StorageService', 'Image supprimée avec succès');
_errorService.logSuccess('StorageService', 'Image deleted successfully');
} catch (e) {
_errorService.logError('StorageService', 'Erreur suppression image: $e');
// Ne pas rethrow pour éviter de bloquer la suppression de la dépense
_errorService.logError('StorageService', 'Error deleting image: $e');
// Don't rethrow to avoid blocking expense deletion
}
}
/// Compresser une image pour optimiser le stockage
/// Compresses an image to optimize storage space and upload speed.
///
/// Reads the image file, decodes it, resizes it if too large (max 1024x1024),
/// and encodes it as JPEG with 85% quality for optimal balance between
/// file size and image quality.
///
/// Args:
/// [imageFile]: The image file to compress
///
/// Returns:
/// A Future<Uint8List> containing the compressed image bytes
///
/// Throws:
/// Exception if the image cannot be decoded or processed
Future<Uint8List> _compressImage(File imageFile) async {
try {
// Lire l'image
// Read image
final bytes = await imageFile.readAsBytes();
img.Image? image = img.decodeImage(bytes);
if (image == null) {
throw Exception('Impossible de décoder l\'image');
throw Exception('Unable to decode image');
}
// Redimensionner si l'image est trop grande
// Resize if image is too large
const maxWidth = 1024;
const maxHeight = 1024;
@@ -104,50 +172,78 @@ class StorageService {
);
}
// Encoder en JPEG avec compression
// Encode as JPEG with compression
final compressedBytes = img.encodeJpg(image, quality: 85);
_errorService.logInfo('StorageService',
'Image compressée: ${bytes.length}${compressedBytes.length} bytes');
'Image compressed: ${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
_errorService.logError('StorageService', 'Error compressing image: $e');
// Fallback: return original image if compression fails
return await imageFile.readAsBytes();
}
}
/// Valider le fichier image
/// Validates an image file before upload.
///
/// Checks file existence, size constraints (max 10MB), and file extension
/// to ensure only valid image files are processed for upload.
///
/// Args:
/// [imageFile]: The image file to validate
///
/// Throws:
/// Exception if validation fails (file doesn't exist, too large, or invalid extension)
void _validateImageFile(File imageFile) {
// Vérifier que le fichier existe
// Check if file exists
if (!imageFile.existsSync()) {
throw Exception('Le fichier image n\'existe pas');
throw Exception('Image file does not exist');
}
// Vérifier la taille du fichier (max 10MB)
// Check file size (max 10MB)
const maxSizeBytes = 10 * 1024 * 1024; // 10MB
final fileSize = imageFile.lengthSync();
if (fileSize > maxSizeBytes) {
throw Exception('La taille du fichier dépasse 10MB');
throw Exception('File size exceeds 10MB limit');
}
// Vérifier l'extension
// Check file 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');
throw Exception('Unsupported image format. Use JPG, PNG or WebP');
}
}
/// Générer un nom de fichier unique pour un reçu
/// Generates a unique filename for a receipt image.
///
/// Creates a filename using timestamp, microseconds, and group ID to ensure
/// uniqueness and prevent naming conflicts when multiple receipts are uploaded.
///
/// Args:
/// [groupId]: ID of the group this receipt belongs to
///
/// Returns:
/// A unique filename string for the receipt image
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)
/// Uploads multiple images simultaneously (for future features).
///
/// Processes multiple image files in parallel for batch upload scenarios.
/// Each image is validated, compressed, and uploaded with unique filenames.
///
/// Args:
/// [groupId]: ID of the group these images belong to
/// [imageFiles]: List of image files to upload
///
/// Returns:
/// A Future<List<String>> containing download URLs of uploaded images
Future<List<String>> uploadMultipleImages(
String groupId,
List<File> imageFiles,