feat: Implement account management functionality with loading, creation, and error handling
This commit is contained in:
@@ -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'),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user