feat: Implement account management features

- Added ExpenseDetailDialog for displaying expense details and actions.
- Created ExpensesTab to list expenses for a group.
- Developed GroupExpensesPage to manage group expenses with tabs for expenses, balances, and settlements.
- Introduced SettlementsTab to show optimized repayment plans.
- Refactored create_trip_content.dart to remove CountBloc and related logic.
- Added Account model to manage user accounts and group members.
- Replaced CountRepository with AccountRepository for account-related operations.
- Removed CountService and CountRepository as part of the refactor.
- Updated main.dart and home.dart to integrate new account management components.
This commit is contained in:
Dayron
2025-10-21 00:42:36 +02:00
parent a3ced0e812
commit c69618cbd9
21 changed files with 182 additions and 747 deletions

View File

View File

View File

View File

@@ -1,196 +0,0 @@
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../data/models/expense.dart';
import '../../services/count_service.dart';
import '../../repositories/count_repository.dart';
import 'count_event.dart';
import 'count_state.dart';
class CountBloc extends Bloc<CountEvent, CountState> {
final CountService _countService;
StreamSubscription<List<Expense>>? _expensesSubscription;
Map<ExpenseCurrency, double> _exchangeRates = {};
CountBloc({CountService? countService})
: _countService = countService ?? CountService(
countRepository: CountRepository(),
),
super(CountInitial()) {
on<LoadExpenses>(_onLoadExpenses);
on<CreateExpense>(_onCreateExpense);
on<UpdateExpense>(_onUpdateExpense);
on<DeleteExpense>(_onDeleteExpense);
on<ArchiveExpense>(_onArchiveExpense);
on<MarkSplitAsPaid>(_onMarkSplitAsPaid);
on<LoadExchangeRates>(_onLoadExchangeRates);
on<_ExpensesUpdated>(_onExpensesUpdated);
}
Future<void> _onLoadExpenses(
LoadExpenses event,
Emitter<CountState> emit,
) async {
emit(CountLoading());
// Charger les taux de change
if (_exchangeRates.isEmpty) {
_exchangeRates = await _countService.getExchangeRates();
}
await _expensesSubscription?.cancel();
_expensesSubscription = _countService
.getExpensesStream(event.groupId, includeArchived: event.includeArchived)
.listen(
(expenses) {
add(_ExpensesUpdated(
groupId: event.groupId,
expenses: expenses,
));
},
onError: (error) {
add(_ExpensesError('Erreur lors du chargement des dépenses: $error'));
},
);
}
void _onExpensesUpdated(
_ExpensesUpdated event,
Emitter<CountState> emit,
) {
// Récupérer les membres du groupe et calculer les balances
final memberIds = <String>{};
final memberNames = <String, String>{};
for (final expense in event.expenses) {
memberIds.add(expense.paidById);
memberNames[expense.paidById] = expense.paidByName;
for (final split in expense.splits) {
memberIds.add(split.userId);
memberNames[split.userId] = split.userName;
}
}
final balances = _countService.calculateBalances(
event.expenses,
memberIds.toList(),
memberNames,
);
final settlements = _countService.calculateOptimizedSettlements(balances);
emit(ExpensesLoaded(
groupId: event.groupId,
expenses: event.expenses,
balances: balances,
settlements: settlements,
exchangeRates: _exchangeRates,
));
}
Future<void> _onCreateExpense(
CreateExpense event,
Emitter<CountState> emit,
) async {
try {
await _countService.createExpense(
event.expense,
receiptImage: event.receiptImage,
);
} catch (e) {
emit(CountError('Erreur lors de la création de la dépense: $e'));
}
}
Future<void> _onUpdateExpense(
UpdateExpense event,
Emitter<CountState> emit,
) async {
try {
await _countService.updateExpense(
event.expense,
newReceiptImage: event.newReceiptImage,
);
} catch (e) {
emit(CountError('Erreur lors de la modification de la dépense: $e'));
}
}
Future<void> _onDeleteExpense(
DeleteExpense event,
Emitter<CountState> emit,
) async {
try {
await _countService.deleteExpense(event.groupId, event.expenseId);
} catch (e) {
emit(CountError('Erreur lors de la suppression de la dépense: $e'));
}
}
Future<void> _onArchiveExpense(
ArchiveExpense event,
Emitter<CountState> emit,
) async {
try {
await _countService.archiveExpense(event.groupId, event.expenseId);
} catch (e) {
emit(CountError('Erreur lors de l\'archivage de la dépense: $e'));
}
}
Future<void> _onMarkSplitAsPaid(
MarkSplitAsPaid event,
Emitter<CountState> emit,
) async {
try {
await _countService.markSplitAsPaid(
event.groupId,
event.expenseId,
event.userId,
);
} catch (e) {
emit(CountError('Erreur lors du marquage du paiement: $e'));
}
}
Future<void> _onLoadExchangeRates(
LoadExchangeRates event,
Emitter<CountState> emit,
) async {
try {
_exchangeRates = await _countService.getExchangeRates();
} catch (e) {
emit(CountError('Erreur lors du chargement des taux de change: $e'));
}
}
@override
Future<void> close() {
_expensesSubscription?.cancel();
return super.close();
}
}
// Events internes
class _ExpensesUpdated extends CountEvent {
final String groupId;
final List<Expense> expenses;
const _ExpensesUpdated({
required this.groupId,
required this.expenses,
});
@override
List<Object?> get props => [groupId, expenses];
}
class _ExpensesError extends CountEvent {
final String error;
const _ExpensesError(this.error);
@override
List<Object?> get props => [error];
}

View File

@@ -1,91 +0,0 @@
import 'dart:io';
import 'package:equatable/equatable.dart';
import '../../data/models/expense.dart';
abstract class CountEvent extends Equatable {
const CountEvent();
@override
List<Object?> get props => [];
}
class LoadExpenses extends CountEvent {
final String groupId;
final bool includeArchived;
const LoadExpenses(this.groupId, {this.includeArchived = false});
@override
List<Object?> get props => [groupId, includeArchived];
}
class CreateExpense extends CountEvent {
final Expense expense;
final File? receiptImage;
const CreateExpense({
required this.expense,
this.receiptImage,
});
@override
List<Object?> get props => [expense, receiptImage];
}
class UpdateExpense extends CountEvent {
final Expense expense;
final File? newReceiptImage;
const UpdateExpense({
required this.expense,
this.newReceiptImage,
});
@override
List<Object?> get props => [expense, newReceiptImage];
}
class DeleteExpense extends CountEvent {
final String groupId;
final String expenseId;
const DeleteExpense({
required this.groupId,
required this.expenseId,
});
@override
List<Object?> get props => [groupId, expenseId];
}
class ArchiveExpense extends CountEvent {
final String groupId;
final String expenseId;
const ArchiveExpense({
required this.groupId,
required this.expenseId,
});
@override
List<Object?> get props => [groupId, expenseId];
}
class MarkSplitAsPaid extends CountEvent {
final String groupId;
final String expenseId;
final String userId;
const MarkSplitAsPaid({
required this.groupId,
required this.expenseId,
required this.userId,
});
@override
List<Object?> get props => [groupId, expenseId, userId];
}
class LoadExchangeRates extends CountEvent {
const LoadExchangeRates();
}

View File

@@ -1,42 +0,0 @@
import 'package:equatable/equatable.dart';
import '../../data/models/expense.dart';
import '../../data/models/balance.dart';
abstract class CountState extends Equatable {
const CountState();
@override
List<Object?> get props => [];
}
class CountInitial extends CountState {}
class CountLoading extends CountState {}
class ExpensesLoaded extends CountState {
final String groupId;
final List<Expense> expenses;
final List<Balance> balances;
final List<Settlement> settlements;
final Map<ExpenseCurrency, double> exchangeRates;
const ExpensesLoaded({
required this.groupId,
required this.expenses,
required this.balances,
required this.settlements,
required this.exchangeRates,
});
@override
List<Object?> get props => [groupId, expenses, balances, settlements, exchangeRates];
}
class CountError extends CountState {
final String message;
const CountError(this.message);
@override
List<Object?> get props => [message];
}