import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../../data/providers/friends_provider.dart'; import '../../../domain/entities/friend_request.dart'; import '../../widgets/add_friend_dialog.dart'; import '../../widgets/cards/friend_card.dart'; import '../../widgets/friend_request_card.dart'; import '../../widgets/search_friends.dart'; /// Écran principal pour afficher et gérer la liste des amis. /// /// Cet écran inclut des fonctionnalités de pagination, de recherche, /// et de rafraîchissement manuel de la liste avec design moderne et compact. /// /// **Fonctionnalités:** /// - Affichage de la liste des amis en grille /// - Recherche d'amis /// - Pagination automatique /// - Pull-to-refresh /// - Ajout d'amis class FriendsScreen extends StatefulWidget { const FriendsScreen({required this.userId, super.key}); final String userId; @override State createState() => _FriendsScreenState(); } class _FriendsScreenState extends State with SingleTickerProviderStateMixin { // ============================================================================ // CONTROLLERS // ============================================================================ late ScrollController _scrollController; late TabController _tabController; @override void initState() { super.initState(); _initializeScrollController(); _tabController = TabController(length: 2, vsync: this); _loadFriends(); _loadSentRequests(); _loadReceivedRequests(); } void _initializeScrollController() { _scrollController = ScrollController(); _scrollController.addListener(_onScroll); } @override void dispose() { _scrollController.removeListener(_onScroll); _scrollController.dispose(); _tabController.dispose(); super.dispose(); } // ============================================================================ // ACTIONS // ============================================================================ /// Charge les amis au démarrage. void _loadFriends() { Provider.of(context, listen: false) .fetchFriends(widget.userId); } /// Charge les demandes envoyées au démarrage. void _loadSentRequests() { Provider.of(context, listen: false) .fetchSentRequests(); } /// Charge les demandes reçues au démarrage. void _loadReceivedRequests() { Provider.of(context, listen: false) .fetchReceivedRequests(); } /// Gère le défilement pour la pagination. void _onScroll() { final provider = Provider.of(context, listen: false); if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200 && !provider.isLoading && provider.hasMore) { provider.fetchFriends(widget.userId, loadMore: true); } } /// Gère le rafraîchissement. Future _handleRefresh() async { final provider = Provider.of(context, listen: false); provider.fetchFriends(widget.userId); await Future.delayed(const Duration(milliseconds: 500)); } /// Gère l'ajout d'un ami. void _handleAddFriend() { showDialog( context: context, builder: (context) => AddFriendDialog( onFriendAdded: () { // Rafraîchir la liste des amis et des demandes après l'ajout _loadFriends(); _loadSentRequests(); _loadReceivedRequests(); }, ), ); } // ============================================================================ // BUILD // ============================================================================ @override Widget build(BuildContext context) { final theme = Theme.of(context); return Scaffold( appBar: _buildAppBar(theme), body: _buildBody(theme), floatingActionButton: _buildFloatingActionButton(theme), ); } /// Construit la barre d'application. PreferredSizeWidget _buildAppBar(ThemeData theme) { return AppBar( title: const Text('Mes Amis'), bottom: TabBar( controller: _tabController, tabs: const [ Tab(text: 'Amis', icon: Icon(Icons.people)), Tab(text: 'Demandes', icon: Icon(Icons.person_add)), ], ), actions: [ IconButton( icon: const Icon(Icons.refresh), tooltip: 'Actualiser', onPressed: () { _loadFriends(); _loadSentRequests(); _loadReceivedRequests(); }, ), ], ); } /// Construit le corps de l'écran. Widget _buildBody(ThemeData theme) { return SafeArea( child: TabBarView( controller: _tabController, children: [ // Onglet Amis Column( children: [ _buildSearchBar(), Expanded( child: Consumer( builder: (context, provider, child) { if (provider.isLoading && provider.friendsList.isEmpty) { return _buildLoadingState(theme); } if (provider.friendsList.isEmpty) { return _buildEmptyState(theme); } return _buildFriendsList(theme, provider); }, ), ), ], ), // Onglet Demandes en attente _buildPendingRequestsTab(theme), ], ), ); } /// Construit la barre de recherche. Widget _buildSearchBar() { return const Padding( padding: EdgeInsets.all(16), child: SearchFriends(), ); } /// Construit l'état de chargement. Widget _buildLoadingState(ThemeData theme) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator( color: theme.colorScheme.primary, ), const SizedBox(height: 16), Text( 'Chargement des amis...', style: theme.textTheme.bodyMedium, ), ], ), ); } /// Construit l'état vide. Widget _buildEmptyState(ThemeData theme) { return Center( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.people_outline, size: 64, color: theme.colorScheme.secondary.withOpacity(0.6), ), const SizedBox(height: 24), Text( 'Aucun ami trouvé', style: theme.textTheme.titleLarge, textAlign: TextAlign.center, ), const SizedBox(height: 8), Text( 'Commencez à ajouter des amis pour voir leurs événements', style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurface.withOpacity(0.6), ), textAlign: TextAlign.center, ), ], ), ), ); } /// Construit la liste des amis. Widget _buildFriendsList(ThemeData theme, FriendsProvider provider) { return RefreshIndicator( onRefresh: _handleRefresh, color: theme.colorScheme.primary, child: GridView.builder( controller: _scrollController, padding: const EdgeInsets.all(16), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 16, mainAxisSpacing: 16, childAspectRatio: 0.75, ), itemCount: provider.friendsList.length, itemBuilder: (context, index) { final friend = provider.friendsList[index]; return FriendCard(friend: friend); }, ), ); } /// Construit le bouton flottant (compact). Widget _buildFloatingActionButton(ThemeData theme) { return FloatingActionButton( onPressed: _handleAddFriend, tooltip: 'Ajouter un ami', child: const Icon(Icons.person_add), ); } /// Construit l'onglet des demandes en attente avec deux sections. Widget _buildPendingRequestsTab(ThemeData theme) { return Consumer( builder: (context, provider, child) { final isLoading = provider.isLoadingReceivedRequests || provider.isLoadingSentRequests; final hasReceived = provider.receivedRequests.isNotEmpty; final hasSent = provider.sentRequests.isNotEmpty; if (isLoading && !hasReceived && !hasSent) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator( color: theme.colorScheme.primary, ), const SizedBox(height: 16), Text( 'Chargement des demandes...', style: theme.textTheme.bodyMedium, ), ], ), ); } if (!hasReceived && !hasSent) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.person_add_disabled, size: 64, color: theme.colorScheme.onSurface.withOpacity(0.5), ), const SizedBox(height: 16), Text( 'Aucune demande en attente', style: theme.textTheme.titleMedium?.copyWith( color: theme.colorScheme.onSurface.withOpacity(0.7), ), ), const SizedBox(height: 8), Text( 'Les demandes d\'amitié apparaîtront ici', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurface.withOpacity(0.5), ), ), ], ), ); } return RefreshIndicator( onRefresh: () async { await Future.wait([ provider.fetchReceivedRequests(), provider.fetchSentRequests(), ]); }, child: ListView( padding: const EdgeInsets.symmetric(vertical: 8), children: [ // Section Demandes reçues if (hasReceived) ...[ Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Text( 'Demandes reçues', style: theme.textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, ), ), ), ...provider.receivedRequests.map((request) => FriendRequestCard( request: request, onAccept: () => _handleAcceptRequest(provider, request.friendshipId), onReject: () => _handleRejectRequest(provider, request.friendshipId), )), const SizedBox(height: 16), ], // Section Demandes envoyées if (hasSent) ...[ Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Text( 'Demandes envoyées', style: theme.textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, ), ), ), ...provider.sentRequests.map((request) => FriendRequestCard( request: request, onAccept: null, // Pas d'accepter pour les demandes envoyées onReject: () => _handleCancelRequest(provider, request.friendshipId), isSentRequest: true, // Indique que c'est une demande envoyée )), ], ], ), ); }, ); } /// Gère l'acceptation d'une demande d'amitié. Future _handleAcceptRequest(FriendsProvider provider, String friendshipId) async { try { await provider.acceptFriendRequest(friendshipId); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Demande d\'amitié acceptée'), backgroundColor: Colors.green, behavior: SnackBarBehavior.floating, ), ); // Rafraîchir les deux onglets _loadFriends(); _loadReceivedRequests(); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Erreur: ${e.toString()}'), backgroundColor: Colors.red, behavior: SnackBarBehavior.floating, ), ); } } } /// Gère le rejet d'une demande d'amitié. Future _handleRejectRequest(FriendsProvider provider, String friendshipId) async { try { await provider.rejectFriendRequest(friendshipId); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Demande d\'amitié rejetée'), backgroundColor: Colors.orange, behavior: SnackBarBehavior.floating, ), ); _loadReceivedRequests(); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Erreur: ${e.toString()}'), backgroundColor: Colors.red, behavior: SnackBarBehavior.floating, ), ); } } } /// Gère l'annulation d'une demande d'amitié envoyée. Future _handleCancelRequest(FriendsProvider provider, String friendshipId) async { try { await provider.cancelFriendRequest(friendshipId); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Demande d\'amitié annulée'), backgroundColor: Colors.blue, behavior: SnackBarBehavior.floating, ), ); _loadSentRequests(); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Erreur: ${e.toString()}'), backgroundColor: Colors.red, behavior: SnackBarBehavior.floating, ), ); } } } }