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:
@@ -2,14 +2,32 @@ import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';
|
||||
import 'package:travel_mate/services/error_service.dart';
|
||||
|
||||
/// Service for handling Firebase authentication operations.
|
||||
///
|
||||
/// This service provides methods for user authentication including email/password
|
||||
/// sign-in, Google sign-in, Apple sign-in, password reset, and account management.
|
||||
/// It acts as a wrapper around Firebase Auth functionality.
|
||||
class AuthService {
|
||||
/// Error service for logging authentication errors.
|
||||
final _errorService = ErrorService();
|
||||
|
||||
/// Firebase Auth instance for authentication operations.
|
||||
final FirebaseAuth firebaseAuth = FirebaseAuth.instance;
|
||||
|
||||
/// Gets the currently authenticated user.
|
||||
///
|
||||
/// Returns null if no user is currently signed in.
|
||||
User? get currentUser => firebaseAuth.currentUser;
|
||||
|
||||
/// Stream that emits authentication state changes.
|
||||
///
|
||||
/// Emits the current user when authenticated, null when not authenticated.
|
||||
Stream<User?> get authStateChanges => firebaseAuth.authStateChanges();
|
||||
|
||||
/// Signs in a user with email and password.
|
||||
///
|
||||
/// Returns a [UserCredential] containing the authenticated user's information.
|
||||
/// Throws [FirebaseAuthException] if authentication fails.
|
||||
Future<UserCredential> signInWithEmailAndPassword({
|
||||
required String email,
|
||||
required String password
|
||||
@@ -18,6 +36,10 @@ class AuthService {
|
||||
email: email, password: password);
|
||||
}
|
||||
|
||||
/// Creates a new user account with email and password.
|
||||
///
|
||||
/// Returns a [UserCredential] containing the new user's information.
|
||||
/// Throws [FirebaseAuthException] if account creation fails.
|
||||
Future<UserCredential> signUpWithEmailAndPassword({
|
||||
required String email,
|
||||
required String password
|
||||
@@ -26,52 +48,88 @@ class AuthService {
|
||||
email: email, password: password);
|
||||
}
|
||||
|
||||
/// Signs out the current user.
|
||||
///
|
||||
/// Clears the authentication state and signs out the user from Firebase.
|
||||
Future<void> signOut() async {
|
||||
await firebaseAuth.signOut();
|
||||
}
|
||||
|
||||
/// Sends a password reset email to the specified email address.
|
||||
///
|
||||
/// The user will receive an email with instructions to reset their password.
|
||||
/// Throws [FirebaseAuthException] if the email is invalid or other errors occur.
|
||||
Future<void> resetPassword(String email) async {
|
||||
await firebaseAuth.sendPasswordResetEmail(email: email);
|
||||
}
|
||||
|
||||
/// Updates the display name of the current user.
|
||||
///
|
||||
/// Requires a user to be currently authenticated.
|
||||
/// Throws if no user is signed in.
|
||||
Future<void> updateDisplayName({
|
||||
required String displayName,
|
||||
}) async {
|
||||
await currentUser!.updateDisplayName(displayName);
|
||||
}
|
||||
|
||||
/// Deletes the current user's account permanently.
|
||||
///
|
||||
/// Requires re-authentication with the user's current password for security.
|
||||
/// This operation cannot be undone.
|
||||
///
|
||||
/// [password] - The user's current password for re-authentication
|
||||
/// [email] - The user's email address for re-authentication
|
||||
Future<void> deleteAccount({
|
||||
required String password,
|
||||
required String email,
|
||||
}) async {
|
||||
// Re-authenticate the user
|
||||
// Re-authenticate the user for security
|
||||
AuthCredential credential =
|
||||
EmailAuthProvider.credential(email: email, password: password);
|
||||
await currentUser!.reauthenticateWithCredential(credential);
|
||||
|
||||
// Delete the user
|
||||
// Delete the user account permanently
|
||||
await currentUser!.delete();
|
||||
await firebaseAuth.signOut();
|
||||
}
|
||||
|
||||
/// Resets the user's password after re-authentication.
|
||||
///
|
||||
/// This method allows users to change their password by providing their
|
||||
/// current password for security verification.
|
||||
///
|
||||
/// [currentPassword] - The user's current password for verification
|
||||
/// [newPassword] - The new password to set
|
||||
/// [email] - The user's email address for re-authentication
|
||||
Future<void> resetPasswordFromCurrentPassword({
|
||||
required String currentPassword,
|
||||
required String newPassword,
|
||||
required String email,
|
||||
}) async {
|
||||
// Re-authenticate the user
|
||||
// Re-authenticate the user for security
|
||||
AuthCredential credential =
|
||||
EmailAuthProvider.credential(email: email, password: currentPassword);
|
||||
await currentUser!.reauthenticateWithCredential(credential);
|
||||
|
||||
// Update the password
|
||||
// Update to the new password
|
||||
await currentUser!.updatePassword(newPassword);
|
||||
}
|
||||
|
||||
/// Ensures Google Sign-In is properly initialized.
|
||||
///
|
||||
/// This method must be called before attempting Google authentication.
|
||||
Future<void> ensureInitialized(){
|
||||
return GoogleSignInPlatform.instance.init(const InitParameters());
|
||||
}
|
||||
|
||||
/// Signs in a user using Google authentication.
|
||||
///
|
||||
/// Handles the complete Google Sign-In flow including platform initialization
|
||||
/// and credential exchange with Firebase.
|
||||
///
|
||||
/// Returns a [UserCredential] containing the authenticated user's information.
|
||||
/// Throws various exceptions if authentication fails.
|
||||
Future<UserCredential> signInWithGoogle() async {
|
||||
try {
|
||||
await ensureInitialized();
|
||||
@@ -86,24 +144,28 @@ class AuthService {
|
||||
final OAuthCredential credential = GoogleAuthProvider.credential(idToken: idToken);
|
||||
UserCredential userCredential = await firebaseAuth.signInWithCredential(credential);
|
||||
|
||||
// Retourner le UserCredential au lieu de void
|
||||
// Return the UserCredential instead of void
|
||||
return userCredential;
|
||||
}
|
||||
|
||||
} on GoogleSignInException catch (e) {
|
||||
_errorService.logError('Erreur Google Sign-In: $e', StackTrace.current);
|
||||
_errorService.logError('Google Sign-In error: $e', StackTrace.current);
|
||||
rethrow;
|
||||
} on FirebaseAuthException catch (e) {
|
||||
_errorService.logError('Erreur Firebase lors de l\'initialisation de Google Sign-In: $e', StackTrace.current);
|
||||
_errorService.logError('Firebase error during Google Sign-In initialization: $e', StackTrace.current);
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
_errorService.logError('Erreur inconnue lors de l\'initialisation de Google Sign-In: $e', StackTrace.current);
|
||||
_errorService.logError('Unknown error during Google Sign-In initialization: $e', StackTrace.current);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Signs in a user using Apple authentication.
|
||||
///
|
||||
/// TODO: Implement Apple Sign-In functionality
|
||||
/// This method is currently a placeholder for future Apple authentication support.
|
||||
Future signInWithApple() async {
|
||||
// TODO: Implémenter la connexion avec Apple
|
||||
// TODO: Implement Apple sign-in
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,18 +1,40 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../components/error/error_content.dart';
|
||||
|
||||
/// Service for handling application errors and user notifications.
|
||||
///
|
||||
/// This singleton service provides centralized error handling capabilities
|
||||
/// including displaying error dialogs, snackbars, and logging errors for
|
||||
/// debugging purposes. It uses a global navigator key to show notifications
|
||||
/// from anywhere in the application.
|
||||
class ErrorService {
|
||||
static final ErrorService _instance = ErrorService._internal();
|
||||
|
||||
/// Factory constructor that returns the singleton instance.
|
||||
factory ErrorService() => _instance;
|
||||
|
||||
/// Private constructor for singleton pattern.
|
||||
ErrorService._internal();
|
||||
|
||||
// GlobalKey pour accéder au context depuis n'importe où
|
||||
/// Global navigator key for accessing context from anywhere in the app.
|
||||
///
|
||||
/// This key should be assigned to the MaterialApp's navigatorKey property
|
||||
/// to enable error notifications from any part of the application.
|
||||
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
// Afficher une erreur en dialog
|
||||
/// Displays an error message in a dialog.
|
||||
///
|
||||
/// Shows a modal dialog with the error message and optional retry functionality.
|
||||
/// The dialog appearance can be customized with different icons and colors.
|
||||
///
|
||||
/// [message] - The error message to display
|
||||
/// [title] - The dialog title (defaults to 'Error')
|
||||
/// [onRetry] - Optional callback for retry functionality
|
||||
/// [icon] - Icon to display in the dialog
|
||||
/// [iconColor] - Color of the icon
|
||||
void showError({
|
||||
required String message,
|
||||
String title = 'Erreur',
|
||||
String title = 'Error',
|
||||
VoidCallback? onRetry,
|
||||
IconData icon = Icons.error_outline,
|
||||
Color? iconColor,
|
||||
@@ -30,7 +52,14 @@ class ErrorService {
|
||||
}
|
||||
}
|
||||
|
||||
// Afficher une erreur en snackbar
|
||||
/// Displays an error or success message in a snackbar.
|
||||
///
|
||||
/// Shows a floating snackbar at the bottom of the screen with the message.
|
||||
/// The appearance changes based on whether it's an error or success message.
|
||||
///
|
||||
/// [message] - The message to display
|
||||
/// [onRetry] - Optional callback for retry functionality
|
||||
/// [isError] - Whether this is an error (true) or success (false) message
|
||||
void showSnackbar({
|
||||
required String message,
|
||||
VoidCallback? onRetry,
|
||||
@@ -45,7 +74,7 @@ class ErrorService {
|
||||
duration: const Duration(seconds: 4),
|
||||
action: onRetry != null
|
||||
? SnackBarAction(
|
||||
label: 'Réessayer',
|
||||
label: 'Retry',
|
||||
textColor: Colors.white,
|
||||
onPressed: onRetry,
|
||||
)
|
||||
@@ -59,10 +88,17 @@ class ErrorService {
|
||||
}
|
||||
}
|
||||
|
||||
// Logger dans la console (développement)
|
||||
/// Logs error messages to the console during development.
|
||||
///
|
||||
/// Formats and displays error information including source, error message,
|
||||
/// and optional stack trace in a visually distinct format.
|
||||
///
|
||||
/// [source] - The source or location where the error occurred
|
||||
/// [error] - The error object or message
|
||||
/// [stackTrace] - Optional stack trace for debugging
|
||||
void logError(String source, dynamic error, [StackTrace? stackTrace]) {
|
||||
print('═══════════════════════════════════');
|
||||
print('❌ ERREUR dans $source');
|
||||
print('❌ ERROR in $source');
|
||||
print('Message: $error');
|
||||
if (stackTrace != null) {
|
||||
print('StackTrace: $stackTrace');
|
||||
@@ -70,12 +106,18 @@ class ErrorService {
|
||||
print('═══════════════════════════════════');
|
||||
}
|
||||
|
||||
// Logger une info (développement)
|
||||
/// Logs informational messages to the console during development.
|
||||
///
|
||||
/// [source] - The source or location of the information
|
||||
/// [message] - The informational message
|
||||
void logInfo(String source, String message) {
|
||||
print('ℹ️ [$source] $message');
|
||||
}
|
||||
|
||||
// Logger un succès
|
||||
/// Logs success messages to the console during development.
|
||||
///
|
||||
/// [source] - The source or location of the success
|
||||
/// [message] - The success message
|
||||
void logSuccess(String source, String message) {
|
||||
print('✅ [$source] $message');
|
||||
}
|
||||
|
||||
@@ -2,13 +2,27 @@ 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
|
||||
import 'storage_service.dart';
|
||||
|
||||
/// Service for managing expense operations with business logic validation.
|
||||
///
|
||||
/// This service provides high-level expense management functionality including
|
||||
/// validation, receipt image uploading, and coordination with the expense repository.
|
||||
/// It acts as a business logic layer between the UI and data persistence.
|
||||
class ExpenseService {
|
||||
/// Repository for expense data operations.
|
||||
final ExpenseRepository _expenseRepository;
|
||||
|
||||
/// Service for error handling and logging.
|
||||
final ErrorService _errorService;
|
||||
|
||||
/// Service for handling file uploads (receipts).
|
||||
final StorageService _storageService;
|
||||
|
||||
/// Creates a new [ExpenseService] with required dependencies.
|
||||
///
|
||||
/// [expenseRepository] is required for data operations.
|
||||
/// [errorService] and [storageService] have default implementations if not provided.
|
||||
ExpenseService({
|
||||
required ExpenseRepository expenseRepository,
|
||||
ErrorService? errorService,
|
||||
@@ -17,13 +31,22 @@ class ExpenseService {
|
||||
_errorService = errorService ?? ErrorService(),
|
||||
_storageService = storageService ?? StorageService();
|
||||
|
||||
// Création avec validation et upload d'image
|
||||
/// Creates an expense with validation and optional receipt image upload.
|
||||
///
|
||||
/// Validates the expense data, uploads receipt image if provided, and
|
||||
/// creates the expense record in the database.
|
||||
///
|
||||
/// [expense] - The expense data to create
|
||||
/// [receiptImage] - Optional receipt image file to upload
|
||||
///
|
||||
/// Returns the ID of the created expense.
|
||||
/// Throws exceptions if validation fails or creation errors occur.
|
||||
Future<String> createExpenseWithValidation(Expense expense, File? receiptImage) async {
|
||||
try {
|
||||
// Validation métier
|
||||
// Business logic validation
|
||||
_validateExpenseData(expense);
|
||||
|
||||
// Upload du reçu si présent
|
||||
// Upload receipt image if provided
|
||||
String? receiptUrl;
|
||||
if (receiptImage != null) {
|
||||
receiptUrl = await _storageService.uploadReceiptImage(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,10 +1,42 @@
|
||||
/// A service that handles trip-related business logic and data operations.
|
||||
///
|
||||
/// This service provides functionality for:
|
||||
/// - Trip creation, updating, and deletion
|
||||
/// - Trip validation and business rule enforcement
|
||||
/// - Location-based trip suggestions
|
||||
/// - Trip statistics and analytics
|
||||
/// - Integration with Firebase Firestore for data persistence
|
||||
///
|
||||
/// The service ensures data integrity by validating trip information
|
||||
/// before database operations and provides comprehensive error handling
|
||||
/// and logging through the ErrorService.
|
||||
///
|
||||
/// Example usage:
|
||||
/// ```dart
|
||||
/// final tripService = TripService();
|
||||
///
|
||||
/// // Create a new trip
|
||||
/// final tripId = await tripService.createTrip(newTrip);
|
||||
///
|
||||
/// // Get trip suggestions for a location
|
||||
/// final suggestions = await tripService.getTripSuggestions('Paris');
|
||||
///
|
||||
/// // Calculate trip statistics
|
||||
/// final stats = await tripService.getTripStatistics(tripId);
|
||||
/// ```
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:travel_mate/services/error_service.dart';
|
||||
import '../models/trip.dart';
|
||||
|
||||
/// Service for managing trip-related operations and business logic.
|
||||
class TripService {
|
||||
/// Service for error handling and logging
|
||||
final _errorService = ErrorService();
|
||||
|
||||
/// Firestore instance for database operations
|
||||
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||
|
||||
/// Collection name for trips in Firestore
|
||||
static const String _tripsCollection = 'trips';
|
||||
|
||||
// Charger tous les voyages
|
||||
|
||||
@@ -3,29 +3,53 @@ import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:travel_mate/services/error_service.dart';
|
||||
import '../blocs/user/user_state.dart';
|
||||
|
||||
/// Service for managing user operations with Firestore and Firebase Auth.
|
||||
///
|
||||
/// This service provides functionality for user management including creating,
|
||||
/// retrieving, updating, and deleting user data in Firestore. It also handles
|
||||
/// user authentication state and provides methods for user profile management.
|
||||
class UserService {
|
||||
/// Error service for logging user operation errors.
|
||||
final _errorService = ErrorService();
|
||||
|
||||
/// Firestore instance for database operations.
|
||||
final FirebaseFirestore _firestore;
|
||||
|
||||
/// Firebase Auth instance for user authentication.
|
||||
final FirebaseAuth _auth;
|
||||
|
||||
/// Collection name for users in Firestore.
|
||||
static const String _usersCollection = 'users';
|
||||
|
||||
/// Creates a new [UserService] with optional Firestore and Auth instances.
|
||||
///
|
||||
/// If [firestore] or [auth] are not provided, the default instances will be used.
|
||||
UserService({
|
||||
FirebaseFirestore? firestore,
|
||||
FirebaseAuth? auth,
|
||||
}) : _firestore = firestore ?? FirebaseFirestore.instance,
|
||||
_auth = auth ?? FirebaseAuth.instance;
|
||||
|
||||
// Obtenir l'utilisateur connecté actuel
|
||||
/// Gets the currently authenticated Firebase user.
|
||||
///
|
||||
/// Returns the [User] object if authenticated, null otherwise.
|
||||
User? getCurrentFirebaseUser() {
|
||||
return _auth.currentUser;
|
||||
}
|
||||
|
||||
// Obtenir l'ID de l'utilisateur connecté
|
||||
/// Gets the ID of the currently authenticated user.
|
||||
///
|
||||
/// Returns the user ID string if authenticated, null otherwise.
|
||||
String? getCurrentUserId() {
|
||||
return _auth.currentUser?.uid;
|
||||
}
|
||||
|
||||
// Créer un nouvel utilisateur dans Firestore
|
||||
/// Creates a new user document in Firestore.
|
||||
///
|
||||
/// Takes a [UserModel] object and stores it in the users collection.
|
||||
/// Returns true if successful, false if an error occurs.
|
||||
///
|
||||
/// [user] - The user model to create in Firestore
|
||||
Future<bool> createUser(UserModel user) async {
|
||||
try {
|
||||
await _firestore
|
||||
@@ -34,12 +58,16 @@ class UserService {
|
||||
.set(user.toJson());
|
||||
return true;
|
||||
} catch (e) {
|
||||
_errorService.logError('Erreur lors de la création de l\'utilisateur: $e', StackTrace.current);
|
||||
_errorService.logError('Error creating user: $e', StackTrace.current);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Obtenir un utilisateur par son ID
|
||||
/// Retrieves a user by their ID from Firestore.
|
||||
///
|
||||
/// Returns a [UserModel] if the user exists, null otherwise.
|
||||
///
|
||||
/// [userId] - The ID of the user to retrieve
|
||||
Future<UserModel?> getUserById(String userId) async {
|
||||
try {
|
||||
final doc = await _firestore
|
||||
|
||||
Reference in New Issue
Block a user