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:
Dayron
2025-10-21 16:02:58 +02:00
parent 62eb434548
commit 4edbd1cf34
60 changed files with 1973 additions and 342 deletions

View File

@@ -0,0 +1,136 @@
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../repositories/expense_repository.dart';
import '../../services/expense_service.dart';
import '../../services/error_service.dart';
import 'expense_event.dart';
import 'expense_state.dart';
class ExpenseBloc extends Bloc<ExpenseEvent, ExpenseState> {
final ExpenseRepository _expenseRepository;
final ExpenseService _expenseService;
final ErrorService _errorService;
StreamSubscription? _expensesSubscription;
ExpenseBloc({
required ExpenseRepository expenseRepository,
ExpenseService? expenseService,
ErrorService? errorService,
}) : _expenseRepository = expenseRepository,
_expenseService = expenseService ?? ExpenseService(expenseRepository: expenseRepository),
_errorService = errorService ?? ErrorService(),
super(ExpenseInitial()) {
on<LoadExpensesByGroup>(_onLoadExpensesByGroup);
on<ExpensesUpdated>(_onExpensesUpdated);
on<CreateExpense>(_onCreateExpense);
on<UpdateExpense>(_onUpdateExpense);
on<DeleteExpense>(_onDeleteExpense);
on<MarkSplitAsPaid>(_onMarkSplitAsPaid);
on<ArchiveExpense>(_onArchiveExpense);
}
Future<void> _onLoadExpensesByGroup(
LoadExpensesByGroup event,
Emitter<ExpenseState> emit,
) async {
try {
emit(ExpenseLoading());
await _expensesSubscription?.cancel();
_expensesSubscription = _expenseRepository
.getExpensesStream(event.groupId)
.listen(
(expenses) => add(ExpensesUpdated(expenses)),
onError: (error) => add(ExpensesUpdated([], error: error.toString())),
);
} catch (e) {
_errorService.logError('ExpenseBloc', 'Erreur chargement expenses: $e');
emit(ExpenseError(e.toString()));
}
}
Future<void> _onExpensesUpdated(
ExpensesUpdated event,
Emitter<ExpenseState> emit,
) async {
if (event.error != null) {
emit(ExpenseError(event.error!));
} else {
emit(ExpensesLoaded(expenses: event.expenses));
}
}
Future<void> _onCreateExpense(
CreateExpense event,
Emitter<ExpenseState> emit,
) async {
try {
await _expenseService.createExpenseWithValidation(event.expense, event.receiptImage);
emit(const ExpenseOperationSuccess('Dépense créée avec succès'));
} catch (e) {
_errorService.logError('ExpenseBloc', 'Erreur création expense: $e');
emit(ExpenseError(e.toString()));
}
}
Future<void> _onUpdateExpense(
UpdateExpense event,
Emitter<ExpenseState> emit,
) async {
try {
await _expenseService.updateExpenseWithValidation(event.expense, event.newReceiptImage);
emit(const ExpenseOperationSuccess('Dépense modifiée avec succès'));
} catch (e) {
_errorService.logError('ExpenseBloc', 'Erreur mise à jour expense: $e');
emit(ExpenseError(e.toString()));
}
}
Future<void> _onDeleteExpense(
DeleteExpense event,
Emitter<ExpenseState> emit,
) async {
try {
await _expenseRepository.deleteExpense(event.expenseId);
emit(const ExpenseOperationSuccess('Dépense supprimée avec succès'));
} catch (e) {
_errorService.logError('ExpenseBloc', 'Erreur suppression expense: $e');
emit(ExpenseError(e.toString()));
}
}
Future<void> _onMarkSplitAsPaid(
MarkSplitAsPaid event,
Emitter<ExpenseState> emit,
) async {
try {
await _expenseRepository.markSplitAsPaid(event.expenseId, event.userId);
emit(const ExpenseOperationSuccess('Paiement marqué comme effectué'));
} catch (e) {
_errorService.logError('ExpenseBloc', 'Erreur mark split paid: $e');
emit(ExpenseError(e.toString()));
}
}
Future<void> _onArchiveExpense(
ArchiveExpense event,
Emitter<ExpenseState> emit,
) async {
try {
await _expenseRepository.archiveExpense(event.expenseId);
emit(const ExpenseOperationSuccess('Dépense archivée avec succès'));
} catch (e) {
_errorService.logError('ExpenseBloc', 'Erreur archivage expense: $e');
emit(ExpenseError(e.toString()));
}
}
@override
Future<void> close() {
_expensesSubscription?.cancel();
return super.close();
}
}

View File

@@ -0,0 +1,87 @@
import 'package:equatable/equatable.dart';
import '../../models/expense.dart';
import 'dart:io';
abstract class ExpenseEvent extends Equatable {
const ExpenseEvent();
@override
List<Object?> get props => [];
}
class LoadExpensesByGroup extends ExpenseEvent {
final String groupId;
const LoadExpensesByGroup(this.groupId);
@override
List<Object?> get props => [groupId];
}
class CreateExpense extends ExpenseEvent {
final Expense expense;
final File? receiptImage;
const CreateExpense({
required this.expense,
this.receiptImage,
});
@override
List<Object?> get props => [expense, receiptImage];
}
class UpdateExpense extends ExpenseEvent {
final Expense expense;
final File? newReceiptImage;
const UpdateExpense({
required this.expense,
this.newReceiptImage,
});
@override
List<Object?> get props => [expense, newReceiptImage];
}
class DeleteExpense extends ExpenseEvent {
final String expenseId;
const DeleteExpense(this.expenseId);
@override
List<Object?> get props => [expenseId];
}
class MarkSplitAsPaid extends ExpenseEvent {
final String expenseId;
final String userId;
const MarkSplitAsPaid({
required this.expenseId,
required this.userId,
});
@override
List<Object?> get props => [expenseId, userId];
}
class ArchiveExpense extends ExpenseEvent {
final String expenseId;
const ArchiveExpense(this.expenseId);
@override
List<Object?> get props => [expenseId];
}
// Événement privé pour les mises à jour du stream
class ExpensesUpdated extends ExpenseEvent {
final List<Expense> expenses;
final String? error;
const ExpensesUpdated(this.expenses, {this.error});
@override
List<Object?> get props => [expenses, error];
}

View File

@@ -0,0 +1,44 @@
import 'package:equatable/equatable.dart';
import '../../models/expense.dart';
abstract class ExpenseState extends Equatable {
const ExpenseState();
@override
List<Object?> get props => [];
}
class ExpenseInitial extends ExpenseState {}
class ExpenseLoading extends ExpenseState {}
class ExpensesLoaded extends ExpenseState {
final List<Expense> expenses;
final Map<String, double> exchangeRates;
const ExpensesLoaded({
required this.expenses,
this.exchangeRates = const {'EUR': 1.0, 'USD': 0.85, 'GBP': 1.15},
});
@override
List<Object?> get props => [expenses, exchangeRates];
}
class ExpenseOperationSuccess extends ExpenseState {
final String message;
const ExpenseOperationSuccess(this.message);
@override
List<Object?> get props => [message];
}
class ExpenseError extends ExpenseState {
final String message;
const ExpenseError(this.message);
@override
List<Object?> get props => [message];
}