/// Page liste des conversations v4 library conversations_page; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../shared/design_system/unionflow_design_system.dart'; import '../../domain/entities/conversation.dart'; import '../bloc/messaging_bloc.dart'; import '../bloc/messaging_event.dart'; import '../bloc/messaging_state.dart'; import '../widgets/conversation_tile.dart'; import 'conversation_detail_page.dart'; class ConversationsPage extends StatelessWidget { const ConversationsPage({super.key}); @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; return Scaffold( backgroundColor: scheme.surface, appBar: UFAppBar( title: 'Messages', moduleGradient: ModuleColors.communicationGradient, automaticallyImplyLeading: true, ), body: BlocConsumer( listener: (context, state) { if (state is MessagingError) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(state.message), backgroundColor: ColorTokens.error, ), ); } if (state is ConversationCreee) { _openDetail(context, state.conversation.id); } }, builder: (context, state) { if (state is MessagingLoading) { return const Center(child: CircularProgressIndicator()); } if (state is MesConversationsLoaded) { return _buildList(context, state.conversations); } // État initial ou erreur non fatale — afficher liste vide return _buildEmpty(context); }, ), ); } Widget _buildList(BuildContext context, List conversations) { if (conversations.isEmpty) { return _buildEmpty(context); } // Tri : non lus d'abord, puis par date de dernier message final sorted = List.of(conversations) ..sort((a, b) { if (a.hasUnread && !b.hasUnread) return -1; if (!a.hasUnread && b.hasUnread) return 1; final aDate = a.dernierMessageAt; final bDate = b.dernierMessageAt; if (aDate == null && bDate == null) return 0; if (aDate == null) return 1; if (bDate == null) return -1; return bDate.compareTo(aDate); }); return RefreshIndicator( color: ModuleColors.communication, onRefresh: () async { context.read().add(const LoadMesConversations()); }, child: ListView.separated( padding: const EdgeInsets.all(SpacingTokens.md), itemCount: sorted.length, separatorBuilder: (_, __) => const SizedBox(height: SpacingTokens.sm), itemBuilder: (context, index) { final conv = sorted[index]; return ConversationTile( conversation: conv, onTap: () => _openDetail(context, conv.id), ); }, ), ); } Widget _buildEmpty(BuildContext context) { final scheme = Theme.of(context).colorScheme; return RefreshIndicator( color: ModuleColors.communication, onRefresh: () async { context.read().add(const LoadMesConversations()); }, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: SizedBox( height: MediaQuery.of(context).size.height * 0.6, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.chat_bubble_outline, size: 64, color: scheme.onSurfaceVariant.withOpacity(0.4), ), const SizedBox(height: SpacingTokens.md), Text('Aucune conversation', style: AppTypography.headerSmall), const SizedBox(height: SpacingTokens.sm), Text( 'Contactez un membre ou le bureau\nde votre organisation', style: AppTypography.bodyTextSmall, textAlign: TextAlign.center, ), ], ), ), ), ), ); } void _openDetail(BuildContext context, String conversationId) { Navigator.of(context).push( MaterialPageRoute( builder: (_) => BlocProvider.value( value: context.read(), child: ConversationDetailPage(conversationId: conversationId), ), ), ); } }