feat: Implement account management functionality with loading, creation, and error handling
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
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';
|
||||
|
||||
class AccountBloc extends Bloc<AccountEvent, AccountState> {
|
||||
final AccountRepository _repository;
|
||||
StreamSubscription? _accountsSubscription;
|
||||
final _errorService = ErrorService();
|
||||
|
||||
AccountBloc(this._repository) : super(AccountInitial()) {
|
||||
on<LoadAccountsByUserId>(_onLoadAccountsByUserId);
|
||||
on<_AccountsUpdated>(_onAccountsUpdated);
|
||||
on<CreateAccount>(_onCreateAccount);
|
||||
on<CreateAccountWithMembers>(_onCreateAccountWithMembers);
|
||||
}
|
||||
|
||||
Future<void> _onLoadAccountsByUserId(
|
||||
LoadAccountsByUserId event,
|
||||
Emitter<AccountState> emit,
|
||||
) async {
|
||||
try {
|
||||
emit(AccountLoading());
|
||||
await _accountsSubscription?.cancel();
|
||||
_accountsSubscription = _repository.getAccountByUserId(event.userId).listen(
|
||||
(accounts) {
|
||||
add(_AccountsUpdated(accounts));
|
||||
},
|
||||
onError: (error) {
|
||||
add(_AccountsUpdated([], error: error.toString()));
|
||||
},
|
||||
);
|
||||
} catch (e, stackTrace) {
|
||||
_errorService.logError(e.toString(), stackTrace);
|
||||
emit(AccountError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onAccountsUpdated(
|
||||
_AccountsUpdated event,
|
||||
Emitter<AccountState> emit,
|
||||
) async {
|
||||
if (event.error != null) {
|
||||
_errorService.logError(event.error!, StackTrace.current);
|
||||
emit(AccountError(event.error!));
|
||||
} else {
|
||||
emit(AccountsLoaded(event.accounts));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onCreateAccount(
|
||||
CreateAccount event,
|
||||
Emitter<AccountState> emit,
|
||||
) async {
|
||||
try {
|
||||
emit(AccountLoading());
|
||||
final accountId = await _repository.createAccountWithMembers(
|
||||
account: event.account,
|
||||
members: [],
|
||||
);
|
||||
emit(AccountOperationSuccess('Compte créé avec succès. ID: $accountId'));
|
||||
} catch (e, stackTrace) {
|
||||
_errorService.logError(e.toString(), stackTrace);
|
||||
emit(AccountError('Erreur lors de la création du compte: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onCreateAccountWithMembers(
|
||||
CreateAccountWithMembers event,
|
||||
Emitter<AccountState> emit,
|
||||
) async {
|
||||
try{
|
||||
emit(AccountLoading());
|
||||
final accountId = await _repository.createAccountWithMembers(
|
||||
account: event.account,
|
||||
members: event.members,
|
||||
);
|
||||
emit(AccountOperationSuccess('Compte créé avec succès. ID: $accountId'));
|
||||
} catch (e, stackTrace) {
|
||||
_errorService.logError(e.toString(), stackTrace);
|
||||
emit(AccountError('Erreur lors de la création du compte: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_accountsSubscription?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
class _AccountsUpdated extends AccountEvent {
|
||||
final List<Account> accounts;
|
||||
final String? error;
|
||||
|
||||
const _AccountsUpdated(this.accounts, {this.error});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [accounts, error];
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../data/models/account.dart';
|
||||
import '../../data/models/group_member.dart';
|
||||
|
||||
abstract class AccountEvent extends Equatable {
|
||||
const AccountEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class LoadAccountsByUserId extends AccountEvent {
|
||||
final String userId;
|
||||
|
||||
const LoadAccountsByUserId(this.userId);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [userId];
|
||||
}
|
||||
|
||||
class LoadAccountsByTrip extends AccountEvent {
|
||||
final String tripId;
|
||||
|
||||
const LoadAccountsByTrip(this.tripId);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [tripId];
|
||||
}
|
||||
|
||||
class CreateAccount extends AccountEvent {
|
||||
final Account account;
|
||||
|
||||
const CreateAccount(this.account);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [account];
|
||||
}
|
||||
|
||||
class UpdateAccount extends AccountEvent {
|
||||
final String accountId;
|
||||
final Account account;
|
||||
|
||||
const UpdateAccount({
|
||||
required this.accountId,
|
||||
required this.account,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [accountId, account];
|
||||
}
|
||||
|
||||
class CreateAccountWithMembers extends AccountEvent {
|
||||
final Account account;
|
||||
final List<GroupMember> members;
|
||||
|
||||
const CreateAccountWithMembers({
|
||||
required this.account,
|
||||
required this.members,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [account, members];
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../data/models/account.dart';
|
||||
|
||||
abstract class AccountState extends Equatable {
|
||||
const AccountState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class AccountInitial extends AccountState {}
|
||||
|
||||
class AccountLoading extends AccountState {}
|
||||
|
||||
class AccountsLoaded extends AccountState {
|
||||
final List<Account> accounts;
|
||||
|
||||
const AccountsLoaded(this.accounts);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [accounts];
|
||||
}
|
||||
|
||||
class AccountOperationSuccess extends AccountState {
|
||||
final String message;
|
||||
|
||||
const AccountOperationSuccess(this.message);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
|
||||
class AccountError extends AccountState {
|
||||
final String message;
|
||||
|
||||
const AccountError(this.message);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
@@ -1,143 +1,239 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:travel_mate/blocs/user/user_bloc.dart';
|
||||
import '../../data/models/account.dart';
|
||||
import '../../blocs/account/account_bloc.dart';
|
||||
import '../../blocs/account/account_event.dart';
|
||||
import '../../blocs/account/account_state.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../blocs/group/group_bloc.dart';
|
||||
import '../../blocs/group/group_state.dart';
|
||||
import '../../data/models/group.dart';
|
||||
import 'group_expenses_page.dart';
|
||||
import 'package:travel_mate/components/error/error_content.dart';
|
||||
import '../../blocs/user/user_state.dart' as user_state;
|
||||
|
||||
class CountContent extends StatelessWidget {
|
||||
const CountContent({super.key});
|
||||
|
||||
class AccountContent extends StatefulWidget {
|
||||
const AccountContent({super.key});
|
||||
|
||||
@override
|
||||
State<AccountContent> createState() => _AccountContentState();
|
||||
}
|
||||
|
||||
class _AccountContentState extends State<AccountContent> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// Charger immédiatement sans attendre le prochain frame
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_loadInitialData();
|
||||
});
|
||||
}
|
||||
|
||||
void _loadInitialData() {
|
||||
try {
|
||||
final userState = context.read<UserBloc>().state;
|
||||
|
||||
if (userState is user_state.UserLoaded) {
|
||||
final userId = userState.user.id;
|
||||
context.read<AccountBloc>().add(LoadAccountsByUserId(userId));
|
||||
} else {
|
||||
throw Exception('Utilisateur non connecté');
|
||||
}
|
||||
} catch (e) {
|
||||
ErrorContent(
|
||||
message: 'Erreur lors du chargement des comptes: $e',
|
||||
onRetry: () {},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<GroupBloc, GroupState>(
|
||||
builder: (context, state) {
|
||||
if (state is GroupLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (state is GroupLoaded) {
|
||||
if (state.groups.isEmpty) {
|
||||
return _buildEmptyState();
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: state.groups.length,
|
||||
itemBuilder: (context, index) {
|
||||
final group = state.groups[index];
|
||||
return _buildGroupCard(context, group);
|
||||
},
|
||||
return BlocBuilder<UserBloc, user_state.UserState>(
|
||||
builder: (context, userState) {
|
||||
if (userState is user_state.UserLoading) {
|
||||
return const Scaffold(
|
||||
body: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
if (state is GroupError) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.error, size: 64, color: Colors.red),
|
||||
const SizedBox(height: 16),
|
||||
Text(state.message),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (userState is user_state.UserError) {
|
||||
return ErrorContent(
|
||||
message: 'Erreur utilisateur: ${userState.message}',
|
||||
onRetry: () {},
|
||||
);
|
||||
}
|
||||
|
||||
return _buildEmptyState();
|
||||
},
|
||||
if (userState is! user_state.UserLoaded) {
|
||||
return const Scaffold(
|
||||
body: Center(child: Text('Utilisateur non connecté')),
|
||||
);
|
||||
}
|
||||
final user = userState.user;
|
||||
|
||||
return BlocConsumer<AccountBloc, AccountState>(
|
||||
listener: (context, accountState) {
|
||||
if (accountState is AccountError) {
|
||||
ErrorContent(
|
||||
message: 'Erreur de chargement des comptes: ${accountState.message}',
|
||||
onRetry: () {
|
||||
context.read<AccountBloc>().add(LoadAccountsByUserId(user.id));
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, accountState) {
|
||||
return Scaffold(
|
||||
body: SafeArea(child: _buildContent(accountState, user.id))
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyState() {
|
||||
return const Center(
|
||||
Widget _buildContent(AccountState accountState, String userId) {
|
||||
if (accountState is AccountLoading) {
|
||||
return const Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(),
|
||||
SizedBox(height: 16),
|
||||
Text('Chargement des comptes...'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (accountState is AccountError) {
|
||||
return ErrorContent(
|
||||
message: 'Erreur de chargement des comptes...',
|
||||
onRetry: () {
|
||||
context.read<AccountBloc>().add(LoadAccountsByUserId(userId));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (accountState is AccountsLoaded) {
|
||||
if (accountState.accounts.isEmpty) {
|
||||
return _buildEmptyState();
|
||||
}
|
||||
|
||||
return _buildAccountsList(accountState.accounts, userId);
|
||||
}
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.account_balance_wallet, size: 80, color: Colors.grey),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'Aucun groupe',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
'Créez un groupe pour commencer à gérer vos dépenses',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey),
|
||||
const Text('État inconnu'),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.read<AccountBloc>().add(LoadAccountsByUserId(userId));
|
||||
},
|
||||
child: const Text('Charger les comptes'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGroupCard(BuildContext context, Group group) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => GroupExpensesPage(group: group),
|
||||
Widget _buildEmptyState() {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.account_balance_wallet, size: 80, color: Colors.grey),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
'Aucun compte trouvé',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
);
|
||||
},
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 56,
|
||||
height: 56,
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? Colors.blue[900] : Colors.blue[100],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.group,
|
||||
color: Colors.blue,
|
||||
size: 32,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
group.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${group.members.length} membre${group.members.length > 1 ? 's' : ''}',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: isDark ? Colors.grey[400] : Colors.grey[600],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.chevron_right,
|
||||
color: isDark ? Colors.grey[400] : Colors.grey[600],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'Les comptes sont créés automatiquement lorsque vous créez un voyage',
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAccountsList(List<Account> accounts, String userId) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
context.read<AccountBloc>().add(LoadAccountsByUserId(userId));
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
},
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
const Text(
|
||||
'Mes comptes',
|
||||
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Gérez vos comptes de voyage',
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
...accounts.map((account) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: _buildSimpleAccountCard(account),
|
||||
);
|
||||
})
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSimpleAccountCard(Account account) {
|
||||
try {
|
||||
final colors = [Colors.blue, Colors.purple, Colors.green, Colors.orange];
|
||||
final color = colors[account.name.hashCode.abs() % colors.length];
|
||||
|
||||
String memberInfo = '${account.members.length} membre${account.members.length > 1 ? 's' : ''}';
|
||||
|
||||
if(account.members.isNotEmpty){
|
||||
final names = account.members
|
||||
.take(2)
|
||||
.map((m) => m.pseudo.isNotEmpty ? m.pseudo : m.firstName)
|
||||
.join(', ');
|
||||
memberInfo += '\n$names';
|
||||
}
|
||||
|
||||
return Card(
|
||||
elevation: 2,
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: color,
|
||||
child: const Icon(Icons.account_balance_wallet, color: Colors.white),
|
||||
),
|
||||
title: Text(
|
||||
account.name,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
subtitle: Text(memberInfo),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {},
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
return Card(
|
||||
color: Colors.red,
|
||||
child: const ListTile(
|
||||
leading: Icon(Icons.error, color: Colors.red),
|
||||
title: Text('Erreur d\'affichage'),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../blocs/account/account_bloc.dart';
|
||||
import '../../blocs/account/account_event.dart';
|
||||
import '../../blocs/account/account_state.dart';
|
||||
import '../../blocs/user/user_bloc.dart';
|
||||
import '../../blocs/user/user_state.dart' as user_state;
|
||||
import '../../data/models/group.dart';
|
||||
import 'add_expense_dialog.dart';
|
||||
import 'balances_tab.dart';
|
||||
import 'expenses_tab.dart';
|
||||
import 'settlements_tab.dart';
|
||||
|
||||
class GroupExpensesPage extends StatefulWidget {
|
||||
final Group group;
|
||||
|
||||
const GroupExpensesPage({
|
||||
super.key,
|
||||
required this.group,
|
||||
});
|
||||
|
||||
@override
|
||||
State<GroupExpensesPage> createState() => _GroupExpensesPageState();
|
||||
}
|
||||
|
||||
class _GroupExpensesPageState extends State<GroupExpensesPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late TabController _tabController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: 3, vsync: this);
|
||||
context.read<CountBloc>().add(LoadExpenses(widget.group.id));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.group.name),
|
||||
bottom: TabBar(
|
||||
controller: _tabController,
|
||||
tabs: const [
|
||||
Tab(text: 'Dépenses', icon: Icon(Icons.receipt_long)),
|
||||
Tab(text: 'Balances', icon: Icon(Icons.account_balance)),
|
||||
Tab(text: 'Remboursements', icon: Icon(Icons.payments)),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: BlocConsumer<CountBloc, CountState>(
|
||||
listener: (context, state) {
|
||||
if (state is CountError) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(state.message),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (state is CountLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (state is ExpensesLoaded) {
|
||||
return TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
ExpensesTab(
|
||||
expenses: state.expenses,
|
||||
group: widget.group,
|
||||
),
|
||||
BalancesTab(balances: state.balances),
|
||||
SettlementsTab(settlements: state.settlements),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return const Center(child: Text('Aucune donnée'));
|
||||
},
|
||||
),
|
||||
floatingActionButton: BlocBuilder<UserBloc, user_state.UserState>(
|
||||
builder: (context, userState) {
|
||||
if (userState is! user_state.UserLoaded) return const SizedBox();
|
||||
|
||||
return FloatingActionButton.extended(
|
||||
onPressed: () => _showAddExpenseDialog(context, userState.user),
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('Dépense'),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showAddExpenseDialog(BuildContext context, user_state.UserModel currentUser) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (dialogContext) => BlocProvider.value(
|
||||
value: context.read<CountBloc>(),
|
||||
child: AddExpenseDialog(
|
||||
group: widget.group,
|
||||
currentUser: currentUser,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,14 @@ class Account {
|
||||
final String id;
|
||||
final String tripId;
|
||||
final String groupId;
|
||||
final String name;
|
||||
final List<GroupMember> members;
|
||||
|
||||
Account({
|
||||
required this.id,
|
||||
required this.tripId,
|
||||
required this.groupId,
|
||||
required this.name,
|
||||
List<GroupMember>? members,
|
||||
}) : members = members ?? [];
|
||||
|
||||
@@ -19,6 +21,7 @@ class Account {
|
||||
id: map['id'] as String,
|
||||
tripId: map['tripId'] as String,
|
||||
groupId: map['groupId'] as String,
|
||||
name: map['name'] as String,
|
||||
members: [],
|
||||
);
|
||||
}
|
||||
@@ -28,6 +31,7 @@ class Account {
|
||||
'id': id,
|
||||
'tripId': tripId,
|
||||
'groupId': groupId,
|
||||
'name': name,
|
||||
'members': members.map((member) => member.toMap()).toList(),
|
||||
};
|
||||
}
|
||||
@@ -36,12 +40,14 @@ class Account {
|
||||
String? id,
|
||||
String? tripId,
|
||||
String? groupId,
|
||||
String? name,
|
||||
List<GroupMember>? members,
|
||||
}) {
|
||||
return Account(
|
||||
id: id ?? this.id,
|
||||
tripId: tripId ?? this.tripId,
|
||||
groupId: groupId ?? this.groupId,
|
||||
name: name ?? this.name,
|
||||
members: members ?? this.members,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -103,15 +103,44 @@ class AccountRepository {
|
||||
return await _firestore.collection('accounts').doc(accountId).get();
|
||||
}
|
||||
|
||||
Future<void> createAccount(Map<String, dynamic> accountData) async {
|
||||
await _firestore.collection('accounts').add(accountData);
|
||||
Future<void> updateAccount(String accountId, Account account) async {
|
||||
try {
|
||||
await _firestore.collection('accounts').doc(accountId).update(account.toMap());
|
||||
} catch (e) {
|
||||
_errorService.logError('account_repository.dart', 'Erreur lors de la mise à jour du compte: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateAccount(String accountId, Map<String, dynamic> accountData) async {
|
||||
await _firestore.collection('accounts').doc(accountId).update(accountData);
|
||||
Future<void> deleteAccount(String tripId) async {
|
||||
try {
|
||||
final querySnapshot = await _firestore
|
||||
.collection('accounts')
|
||||
.where('tripId', isEqualTo: tripId)
|
||||
.get();
|
||||
if (querySnapshot.docs.isEmpty) {
|
||||
throw Exception('Aucun compte trouvé pour ce voyage');
|
||||
}
|
||||
|
||||
final docId = querySnapshot.docs.first.id;
|
||||
|
||||
final membersSnapshot = await _membersCollection(docId).get();
|
||||
for (var memberDoc in membersSnapshot.docs) {
|
||||
await _membersCollection(docId).doc(memberDoc.id).delete();
|
||||
}
|
||||
await _accountCollection.doc(docId).delete();
|
||||
} catch (e) {
|
||||
_errorService.logError('account_repository.dart', 'Erreur lors de la suppression du compte: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteAccount(String accountId) async {
|
||||
await _firestore.collection('accounts').doc(accountId).delete();
|
||||
Stream<List<GroupMember>> watchGroupMembers(String accountId) {
|
||||
return _membersCollection(accountId).snapshots().map(
|
||||
(snapshot) => snapshot.docs
|
||||
.map((doc) => GroupMember.fromMap(
|
||||
doc.data() as Map<String, dynamic>,
|
||||
doc.id,
|
||||
))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:travel_mate/services/error_service.dart';
|
||||
import '../data/models/account.dart';
|
||||
|
||||
class AccountService {
|
||||
final _errorService = ErrorService();
|
||||
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||
|
||||
Stream<List<Account>> getAccountsStream() {
|
||||
return _firestore.collection('accounts').snapshots().map((snapshot) {
|
||||
return snapshot.docs.map((doc) {
|
||||
return Account.fromMap(doc.data());
|
||||
}).toList();
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool> createAccount(Account account) async {
|
||||
try {
|
||||
await _firestore.collection('accounts').add(account.toMap());
|
||||
return true;
|
||||
} catch (e) {
|
||||
_errorService.logError('Erreur lors de la création du compte: $e', StackTrace.current);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> updateAccount(Account account) async {
|
||||
try {
|
||||
await _firestore.collection('accounts').doc(account.id).update(account.toMap());
|
||||
return true;
|
||||
} catch (e) {
|
||||
_errorService.logError('Erreur lors de la mise à jour du compte: $e', StackTrace.current);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> deleteAccount(String accountId) async {
|
||||
try {
|
||||
await _firestore.collection('accounts').doc(accountId).delete();
|
||||
return true;
|
||||
} catch (e) {
|
||||
_errorService.logError('Erreur lors de la suppression du compte: $e', StackTrace.current);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Stream<List<Account>> getAccountsStreamByUser(String userId) {
|
||||
return _firestore
|
||||
.collection('accounts')
|
||||
.where('members', arrayContains: userId)
|
||||
.snapshots()
|
||||
.map((snapshot) {
|
||||
return snapshot.docs.map((doc) {
|
||||
final account = Account.fromMap(doc.data());
|
||||
_errorService.logError('Compte: ${account.name}, Membres: ${account.members.length}', StackTrace.current);
|
||||
return account;
|
||||
}).toList();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -58,12 +58,4 @@ class GroupService {
|
||||
}).toList();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> removeMemberFromGroup(String groupId, String memberId) async {
|
||||
// TODO: Implémenter la suppression d'un membre d'un groupe
|
||||
}
|
||||
|
||||
Future<void> addMemberToGroup(String groupId, String memberId) async {
|
||||
// TODO: Implémenter l'ajout d'un membre à un groupe
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user