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:
@@ -4,7 +4,7 @@ import 'package:travel_mate/services/error_service.dart';
|
||||
import 'account_event.dart';
|
||||
import 'account_state.dart';
|
||||
import '../../repositories/account_repository.dart';
|
||||
import '../../data/models/account.dart';
|
||||
import '../../models/account.dart';
|
||||
|
||||
class AccountBloc extends Bloc<AccountEvent, AccountState> {
|
||||
final AccountRepository _repository;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../data/models/account.dart';
|
||||
import '../../data/models/group_member.dart';
|
||||
import '../../models/account.dart';
|
||||
import '../../models/group_member.dart';
|
||||
|
||||
abstract class AccountEvent extends Equatable {
|
||||
const AccountEvent();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../data/models/account.dart';
|
||||
import '../../models/account.dart';
|
||||
|
||||
abstract class AccountState extends Equatable {
|
||||
const AccountState();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../data/models/user.dart';
|
||||
import '../../models/user.dart';
|
||||
|
||||
abstract class AuthState extends Equatable {
|
||||
const AuthState();
|
||||
|
||||
81
lib/blocs/balance/balance_bloc.dart
Normal file
81
lib/blocs/balance/balance_bloc.dart
Normal file
@@ -0,0 +1,81 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../repositories/balance_repository.dart';
|
||||
import '../../repositories/expense_repository.dart';
|
||||
import '../../services/balance_service.dart';
|
||||
import '../../services/error_service.dart';
|
||||
import 'balance_event.dart';
|
||||
import 'balance_state.dart';
|
||||
class BalanceBloc extends Bloc<BalanceEvent, BalanceState> {
|
||||
final BalanceRepository _balanceRepository;
|
||||
final BalanceService _balanceService;
|
||||
final ErrorService _errorService;
|
||||
|
||||
BalanceBloc({
|
||||
required BalanceRepository balanceRepository,
|
||||
required ExpenseRepository expenseRepository,
|
||||
BalanceService? balanceService,
|
||||
ErrorService? errorService,
|
||||
}) : _balanceRepository = balanceRepository,
|
||||
_balanceService = balanceService ?? BalanceService(balanceRepository: balanceRepository, expenseRepository: expenseRepository),
|
||||
_errorService = errorService ?? ErrorService(),
|
||||
super(BalanceInitial()) {
|
||||
on<LoadGroupBalance>(_onLoadGroupBalance);
|
||||
on<RefreshBalance>(_onRefreshBalance);
|
||||
on<MarkSettlementAsCompleted>(_onMarkSettlementAsCompleted);
|
||||
}
|
||||
|
||||
Future<void> _onLoadGroupBalance(
|
||||
LoadGroupBalance event,
|
||||
Emitter<BalanceState> emit,
|
||||
) async {
|
||||
try {
|
||||
emit(BalanceLoading());
|
||||
|
||||
final groupBalance = await _balanceRepository.calculateGroupBalance(event.groupId);
|
||||
emit(BalanceLoaded(groupBalance));
|
||||
} catch (e) {
|
||||
_errorService.logError('BalanceBloc', 'Erreur chargement balance: $e');
|
||||
emit(BalanceError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onRefreshBalance(
|
||||
RefreshBalance event,
|
||||
Emitter<BalanceState> emit,
|
||||
) async {
|
||||
try {
|
||||
// Garde l'état actuel pendant le refresh si possible
|
||||
if (state is! BalanceLoaded) {
|
||||
emit(BalanceLoading());
|
||||
}
|
||||
|
||||
final groupBalance = await _balanceRepository.calculateGroupBalance(event.groupId);
|
||||
emit(BalanceLoaded(groupBalance));
|
||||
} catch (e) {
|
||||
_errorService.logError('BalanceBloc', 'Erreur refresh balance: $e');
|
||||
emit(BalanceError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onMarkSettlementAsCompleted(
|
||||
MarkSettlementAsCompleted event,
|
||||
Emitter<BalanceState> emit,
|
||||
) async {
|
||||
try {
|
||||
await _balanceService.markSettlementAsCompleted(
|
||||
groupId: event.groupId,
|
||||
fromUserId: event.fromUserId,
|
||||
toUserId: event.toUserId,
|
||||
amount: event.amount,
|
||||
);
|
||||
|
||||
emit(const BalanceOperationSuccess('Règlement marqué comme effectué'));
|
||||
|
||||
// Recharger la balance après le règlement
|
||||
add(RefreshBalance(event.groupId));
|
||||
} catch (e) {
|
||||
_errorService.logError('BalanceBloc', 'Erreur mark settlement: $e');
|
||||
emit(BalanceError(e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
43
lib/blocs/balance/balance_event.dart
Normal file
43
lib/blocs/balance/balance_event.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class BalanceEvent extends Equatable {
|
||||
const BalanceEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class LoadGroupBalance extends BalanceEvent {
|
||||
final String groupId;
|
||||
|
||||
const LoadGroupBalance(this.groupId);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [groupId];
|
||||
}
|
||||
|
||||
class RefreshBalance extends BalanceEvent {
|
||||
final String groupId;
|
||||
|
||||
const RefreshBalance(this.groupId);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [groupId];
|
||||
}
|
||||
|
||||
class MarkSettlementAsCompleted extends BalanceEvent {
|
||||
final String groupId;
|
||||
final String fromUserId;
|
||||
final String toUserId;
|
||||
final double amount;
|
||||
|
||||
const MarkSettlementAsCompleted({
|
||||
required this.groupId,
|
||||
required this.fromUserId,
|
||||
required this.toUserId,
|
||||
required this.amount,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [groupId, fromUserId, toUserId, amount];
|
||||
}
|
||||
40
lib/blocs/balance/balance_state.dart
Normal file
40
lib/blocs/balance/balance_state.dart
Normal file
@@ -0,0 +1,40 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../models/group_balance.dart';
|
||||
|
||||
abstract class BalanceState extends Equatable {
|
||||
const BalanceState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class BalanceInitial extends BalanceState {}
|
||||
|
||||
class BalanceLoading extends BalanceState {}
|
||||
|
||||
class BalanceLoaded extends BalanceState {
|
||||
final GroupBalance groupBalance;
|
||||
|
||||
const BalanceLoaded(this.groupBalance);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [groupBalance];
|
||||
}
|
||||
|
||||
class BalanceOperationSuccess extends BalanceState {
|
||||
final String message;
|
||||
|
||||
const BalanceOperationSuccess(this.message);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
|
||||
class BalanceError extends BalanceState {
|
||||
final String message;
|
||||
|
||||
const BalanceError(this.message);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
136
lib/blocs/expense/expense_bloc.dart
Normal file
136
lib/blocs/expense/expense_bloc.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
87
lib/blocs/expense/expense_event.dart
Normal file
87
lib/blocs/expense/expense_event.dart
Normal 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];
|
||||
}
|
||||
44
lib/blocs/expense/expense_state.dart
Normal file
44
lib/blocs/expense/expense_state.dart
Normal 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];
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import 'package:travel_mate/services/error_service.dart';
|
||||
import 'group_event.dart';
|
||||
import 'group_state.dart';
|
||||
import '../../repositories/group_repository.dart';
|
||||
import '../../data/models/group.dart';
|
||||
import '../../models/group.dart';
|
||||
|
||||
class GroupBloc extends Bloc<GroupEvent, GroupState> {
|
||||
final GroupRepository _repository;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../data/models/group.dart';
|
||||
import '../../data/models/group_member.dart';
|
||||
import '../../models/group.dart';
|
||||
import '../../models/group_member.dart';
|
||||
|
||||
abstract class GroupEvent extends Equatable {
|
||||
const GroupEvent();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../data/models/group.dart';
|
||||
import '../../models/group.dart';
|
||||
|
||||
abstract class GroupState extends Equatable {
|
||||
const GroupState();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../data/models/message.dart';
|
||||
import '../../models/message.dart';
|
||||
import '../../services/message_service.dart';
|
||||
import '../../repositories/message_repository.dart';
|
||||
import 'message_event.dart';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../data/models/message.dart';
|
||||
import '../../models/message.dart';
|
||||
|
||||
abstract class MessageState extends Equatable {
|
||||
const MessageState();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:travel_mate/data/models/trip.dart';
|
||||
import 'package:travel_mate/models/trip.dart';
|
||||
import 'trip_event.dart';
|
||||
import 'trip_state.dart';
|
||||
import '../../repositories/trip_repository.dart';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../data/models/trip.dart';
|
||||
import '../../models/trip.dart';
|
||||
|
||||
abstract class TripEvent extends Equatable {
|
||||
const TripEvent();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../data/models/trip.dart';
|
||||
import '../../models/trip.dart';
|
||||
|
||||
abstract class TripState extends Equatable {
|
||||
const TripState();
|
||||
|
||||
Reference in New Issue
Block a user