feat: Introduce memberIds for efficient group querying and management, updating related UI components and .gitignore.

This commit is contained in:
Van Leemput Dayron
2025-11-27 15:36:46 +01:00
parent 9198493dd5
commit cad9d42128
5 changed files with 435 additions and 157 deletions

View File

@@ -455,6 +455,9 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
),
],
),
const SizedBox(height: 32),
_buildNextActivitiesSection(),
_buildExpensesCard(),
],
),
),
@@ -654,52 +657,63 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
return FutureBuilder(
future: _groupRepository.getGroupByTripId(widget.trip.id!),
builder: (context, snapshot) {
// En attente
if (snapshot.connectionState == ConnectionState.waiting) {
builder: (context, groupSnapshot) {
if (groupSnapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
// Erreur
if (snapshot.hasError) {
return Center(
child: Text(
'Erreur: ${snapshot.error}',
style: TextStyle(color: Colors.red),
),
);
}
// Pas de groupe trouvé
if (!snapshot.hasData || snapshot.data == null) {
if (groupSnapshot.hasError ||
!groupSnapshot.hasData ||
groupSnapshot.data == null) {
return const Center(child: Text('Aucun participant'));
}
final group = snapshot.data!;
final members = group.members;
final groupId = groupSnapshot.data!.id;
if (members.isEmpty) {
return const Center(child: Text('Aucun participant'));
}
return StreamBuilder<List<GroupMember>>(
stream: _groupRepository.watchGroupMembers(groupId),
builder: (context, snapshot) {
// En attente
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
...List.generate(members.length, (index) {
final member = members[index];
return Padding(
padding: const EdgeInsets.only(right: 12),
child: _buildParticipantAvatar(member),
);
}),
// Bouton "+" pour ajouter un participant
Padding(
padding: const EdgeInsets.only(right: 12),
child: _buildAddParticipantButton(),
// Erreur
if (snapshot.hasError) {
return Center(
child: Text(
'Erreur: ${snapshot.error}',
style: TextStyle(color: Colors.red),
),
);
}
final members = snapshot.data ?? [];
if (members.isEmpty) {
return const Center(child: Text('Aucun participant'));
}
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
...List.generate(members.length, (index) {
final member = members[index];
return Padding(
padding: const EdgeInsets.only(right: 12),
child: _buildParticipantAvatar(member),
);
}),
// Bouton "+" pour ajouter un participant
Padding(
padding: const EdgeInsets.only(right: 12),
child: _buildAddParticipantButton(),
),
],
),
],
),
);
},
);
},
);
@@ -950,4 +964,175 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
),
);
}
Widget _buildNextActivitiesSection() {
final theme = Theme.of(context);
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Prochaines activités',
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.onSurface,
),
),
TextButton(
onPressed: () => _navigateToActivities(),
child: Text(
'Tout voir',
style: TextStyle(
color: Colors.teal,
fontWeight: FontWeight.w600,
),
),
),
],
),
const SizedBox(height: 8),
_buildActivityCard(
title: 'Visite du Colisée',
date: '11 août, 10:00',
icon: Icons.museum,
),
const SizedBox(height: 12),
_buildActivityCard(
title: 'Dîner à Trastevere',
date: '11 août, 20:30',
icon: Icons.restaurant,
),
],
);
}
Widget _buildActivityCard({
required String title,
required String date,
required IconData icon,
}) {
final theme = Theme.of(context);
final isDarkMode = theme.brightness == Brightness.dark;
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: theme.cardColor,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: isDarkMode
? Colors.white.withValues(alpha: 0.1)
: Colors.black.withValues(alpha: 0.05),
width: 1,
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.teal.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: Colors.teal, size: 24),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: theme.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.onSurface,
),
),
const SizedBox(height: 4),
Text(
date,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface.withValues(alpha: 0.6),
),
),
],
),
),
Icon(
Icons.chevron_right,
color: theme.colorScheme.onSurface.withValues(alpha: 0.4),
),
],
),
);
}
Widget _buildExpensesCard() {
final theme = Theme.of(context);
return Container(
margin: const EdgeInsets.only(top: 24),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFFDF4E3), // Light beige background
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: const BoxDecoration(
color: Colors.orange,
shape: BoxShape.circle,
),
child: const Icon(
Icons.warning_amber_rounded,
color: Colors.white,
size: 24,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Dépenses',
style: theme.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.bold,
color: const Color(0xFF5D4037), // Brown text
),
),
const SizedBox(height: 4),
Text(
'Vous devez 25€ à Clara',
style: theme.textTheme.bodyMedium?.copyWith(
color: const Color(0xFF8D6E63), // Lighter brown
),
),
],
),
),
TextButton(
onPressed: () => _showComingSoon('Régler les dépenses'),
child: Text(
'Régler',
style: TextStyle(
color: const Color(0xFF5D4037),
fontWeight: FontWeight.bold,
),
),
),
],
),
);
}
}