import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:travel_mate/components/home/create_trip_content.dart'; import '../home/show_trip_details_content.dart'; import '../../blocs/user/user_bloc.dart'; import '../../blocs/user/user_state.dart'; import '../../blocs/trip/trip_bloc.dart'; import '../../blocs/trip/trip_state.dart'; import '../../blocs/trip/trip_event.dart'; import '../../models/trip.dart'; /// Home content widget for the main application dashboard. /// /// This widget serves as the primary content area of the home screen, /// displaying user trips and providing navigation to trip management /// features. Key functionality includes: /// - Loading and displaying user trips /// - Creating new trips /// - Viewing trip details /// - Managing trip state with proper user authentication /// /// The widget maintains state persistence using AutomaticKeepAliveClientMixin /// to preserve content when switching between tabs. class HomeContent extends StatefulWidget { /// Creates a home content widget. const HomeContent({super.key}); @override State createState() => _HomeContentState(); } class _HomeContentState extends State with AutomaticKeepAliveClientMixin { /// Preserves widget state when switching between tabs @override bool get wantKeepAlive => true; /// Flag to prevent duplicate trip loading operations bool _hasLoadedTrips = false; @override void initState() { super.initState(); // Use addPostFrameCallback to wait for the widget tree to be ready WidgetsBinding.instance.addPostFrameCallback((_) { _loadTripsIfUserLoaded(); }); } /// Loads trips if a user is currently loaded and trips haven't been loaded yet. /// /// Checks the current user state and initiates trip loading if the user is /// authenticated and trips haven't been loaded previously. This prevents /// duplicate loading operations. void _loadTripsIfUserLoaded() { if (!_hasLoadedTrips && mounted) { final userState = context.read().state; if (userState is UserLoaded) { _hasLoadedTrips = true; context.read().add(LoadTripsByUserId(userId: userState.user.id)); } } } @override Widget build(BuildContext context) { super.build(context); // Important pour AutomaticKeepAliveClientMixin return BlocBuilder( builder: (context, userState) { if (userState is UserLoading) { return Scaffold( body: Center( child: CircularProgressIndicator(), ), ); } if (userState is UserError) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error, size: 64, color: Colors.red), SizedBox(height: 16), Text('Erreur: ${userState.message}'), ], ), ), ); } if (userState is! UserLoaded) { return Scaffold( body: Center( child: Text('Veuillez vous connecter'), ), ); } final user = userState.user; return BlocConsumer( listener: (context, tripState) { if (tripState is TripOperationSuccess) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(tripState.message), backgroundColor: Colors.green, ), ); } else if (tripState is TripError) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(tripState.message), backgroundColor: Colors.red, ), ); } else if (tripState is TripCreated) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Voyage en cours de création...'), backgroundColor: Colors.blue, duration: Duration(seconds: 1), ), ); } }, builder: (context, tripState) { // AJOUTÉ : Si l'état est initial et qu'on n'a pas encore chargé, charger maintenant if (tripState is TripInitial && !_hasLoadedTrips) { WidgetsBinding.instance.addPostFrameCallback((_) { _loadTripsIfUserLoaded(); }); } return Scaffold( body: RefreshIndicator( onRefresh: () async { context.read().add(LoadTripsByUserId(userId: user.id)); await Future.delayed(Duration(milliseconds: 500)); }, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Bonjour ${user.prenom} !', style: TextStyle( fontSize: 28, fontWeight: FontWeight.bold, color: Theme.of(context).brightness == Brightness.dark ? Colors.white : Colors.black, ), ), const SizedBox(height: 8), Text( 'Vos voyages', style: TextStyle( fontSize: 16, color: Theme.of(context).brightness == Brightness.dark ? Colors.white70 : Colors.grey[600], ), ), const SizedBox(height: 20), if (tripState is TripLoading || tripState is TripCreated) _buildLoadingState() else if (tripState is TripError) _buildErrorState(tripState.message, user.id) else if (tripState is TripLoaded) tripState.trips.isEmpty ? _buildEmptyState() : _buildTripsList(tripState.trips) else if (tripState is TripInitial) _buildLoadingState() // Afficher le loader pendant le premier chargement else _buildEmptyState(), const SizedBox(height: 80), ], ), ), ), floatingActionButton: FloatingActionButton( onPressed: () async { final tripBloc = context.read(); final result = await Navigator.push( context, MaterialPageRoute(builder: (context) => const CreateTripContent()), ); if (result == true && mounted) { tripBloc.add(LoadTripsByUserId(userId: user.id)); } }, backgroundColor: Theme.of(context).colorScheme.primary, foregroundColor: Colors.white, child: const Icon(Icons.add), ), floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, ); }, ); }, ); } Widget _buildLoadingState() { return Center( child: Padding( padding: EdgeInsets.all(32), child: CircularProgressIndicator(), ), ); } Widget _buildErrorState(String error, String userId) { return Center( child: Padding( padding: EdgeInsets.all(32), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error, size: 64, color: Colors.red), SizedBox(height: 16), Text('Erreur lors du chargement des voyages'), SizedBox(height: 8), Text( error, textAlign: TextAlign.center, style: TextStyle(fontSize: 12, color: Colors.grey), ), SizedBox(height: 16), ElevatedButton( onPressed: () { context.read().add(LoadTripsByUserId(userId: userId)); }, child: Text('Réessayer'), ), ], ), ), ); } Widget _buildEmptyState() { return Center( child: Padding( padding: EdgeInsets.all(32), child: Column( children: [ Icon(Icons.travel_explore, size: 80, color: Colors.grey[400]), SizedBox(height: 16), Text( 'Aucun voyage', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), SizedBox(height: 8), Text( 'Créez votre premier voyage en appuyant sur le bouton +', textAlign: TextAlign.center, style: TextStyle(fontSize: 14, color: Colors.grey[600]), ), ], ), ), ); } Widget _buildTripsList(List trips) { return Column( children: trips.map((trip) => _buildTripCard(trip)).toList(), ); } Widget _buildTripCard(Trip trip) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; final textColor = isDarkMode ? Colors.white : Colors.black; final subtextColor = isDarkMode ? Colors.white70 : Colors.grey[600]; return Card( margin: EdgeInsets.only(bottom: 12), elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: InkWell( onTap: () async { final result = await Navigator.push( context, MaterialPageRoute( builder: (context) => ShowTripDetailsContent(trip: trip), ), ); if (result == true && mounted) { final userState = context.read().state; if (userState is UserLoaded) { context.read().add(LoadTripsByUserId(userId: userState.user.id)); } } }, borderRadius: BorderRadius.circular(12), child: Padding( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( trip.title, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: textColor, ), ), SizedBox(height: 4), Row( children: [ Icon(Icons.location_on, size: 16, color: subtextColor), SizedBox(width: 4), Text( trip.location, style: TextStyle(color: subtextColor), ), ], ), ], ), ), Container( padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: _getStatusColor(trip).withValues(alpha: 0.2), borderRadius: BorderRadius.circular(12), ), child: Text( _getStatusText(trip), style: TextStyle( color: _getStatusColor(trip), fontWeight: FontWeight.bold, fontSize: 12, ), ), ), ], ), SizedBox(height: 12), Row( children: [ Icon(Icons.calendar_today, size: 16, color: subtextColor), SizedBox(width: 4), Text( '${_formatDate(trip.startDate)} - ${_formatDate(trip.endDate)}', style: TextStyle(fontSize: 14, color: subtextColor), ), ], ), if (trip.budget != null) ...[ SizedBox(height: 8), Row( children: [ Icon(Icons.euro, size: 16, color: subtextColor), SizedBox(width: 4), Text( '${trip.budget!.toStringAsFixed(2)} €', style: TextStyle(fontSize: 14, color: subtextColor), ), ], ), ], SizedBox(height: 8), Row( children: [ Icon(Icons.people, size: 16, color: subtextColor), SizedBox(width: 4), Text( '${trip.participants.length} participant${trip.participants.length > 1 ? 's' : ''}', style: TextStyle(fontSize: 14, color: subtextColor), ), ], ), ], ), ), ), ); } Color _getStatusColor(Trip trip) { final now = DateTime.now(); if (now.isBefore(trip.startDate)) { return Colors.blue; } else if (now.isAfter(trip.endDate)) { return Colors.grey; } else { return Colors.green; } } String _getStatusText(Trip trip) { final now = DateTime.now(); if (now.isBefore(trip.startDate)) { return 'À venir'; } else if (now.isAfter(trip.endDate)) { return 'Terminé'; } else { return 'En cours'; } } String _formatDate(DateTime date) { return '${date.day}/${date.month}/${date.year}'; } }