import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../core/constants.dart'; import '../../core/exceptions.dart'; import '../../providers/chat_provider.dart'; import 'chat_screen.dart'; class ConversationsListScreen extends ConsumerStatefulWidget { const ConversationsListScreen({super.key}); @override ConsumerState createState() => _ConversationsListScreenState(); } class _ConversationsListScreenState extends ConsumerState { int? _selectedConvId; Future _newConversation(bool isWide) async { try { final conv = await ref.read(conversationsProvider.notifier).create(''); if (!mounted) return; if (isWide) { setState(() => _selectedConvId = conv.id); } else { context.push(Routes.chat.replaceFirst(':id', '${conv.id}')); } } on AppException catch (e) { if (mounted) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text(e.message))); } } } @override Widget build(BuildContext context) { final convsAsync = ref.watch(conversationsProvider); final isWide = MediaQuery.of(context).size.width >= 600; // Clear stale selection when switching to narrow mode. if (!isWide && _selectedConvId != null) { _selectedConvId = null; } return Scaffold( body: isWide ? Row( children: [ SizedBox( width: 300, child: Column( children: [ Expanded(child: _buildListPane(convsAsync, isWide)), const Divider(height: 1), ListTile( leading: const Icon(Icons.add), title: const Text('New conversation'), onTap: () => _newConversation(isWide), ), ], ), ), const VerticalDivider(width: 1), Expanded(child: _buildDetailPane()), ], ) : _buildListPane(convsAsync, isWide), floatingActionButton: isWide ? null : FloatingActionButton( heroTag: 'chat_fab', onPressed: () => _newConversation(isWide), child: const Icon(Icons.add), ), ); } Widget _buildDetailPane() { if (_selectedConvId == null) { return const Center(child: Text('Select a conversation to open it.')); } return ChatScreen( key: ValueKey(_selectedConvId), conversationId: _selectedConvId!, ); } Widget _buildListPane(AsyncValue convsAsync, bool isWide) { return convsAsync.when( loading: () => const Center(child: CircularProgressIndicator()), error: (_, _) => Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.cloud_off, size: 48), const SizedBox(height: 12), const Text('Could not load conversations.'), const SizedBox(height: 4), TextButton( onPressed: () => ref.invalidate(conversationsProvider), child: const Text('Retry'), ), ], ), ), data: (convs) { if (convs.isEmpty) { return const Center( child: Text('No conversations yet. Tap + to start one.')); } return RefreshIndicator( onRefresh: () => ref.refresh(conversationsProvider.future), child: ListView.separated( itemCount: convs.length, separatorBuilder: (_, _) => const Divider(height: 1), itemBuilder: (context, i) { final conv = convs[i]; return ListTile( leading: const Icon(Icons.chat_bubble_outline), title: Text( conv.title.isNotEmpty ? conv.title : 'New conversation', ), subtitle: Text( conv.updatedAt.toLocal().toString().substring(0, 16), style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), selected: isWide && _selectedConvId == conv.id, selectedTileColor: Theme.of(context).colorScheme.secondaryContainer, onTap: () { if (isWide) { setState(() => _selectedConvId = conv.id); } else { context.push( Routes.chat.replaceFirst(':id', '${conv.id}'), ); } }, onLongPress: () async { final confirm = await showDialog( context: context, builder: (dialogContext) => AlertDialog( title: const Text('Delete conversation?'), content: Text( conv.title.isNotEmpty ? conv.title : 'New conversation', ), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext, false), child: const Text('Cancel'), ), TextButton( onPressed: () => Navigator.pop(dialogContext, true), child: const Text('Delete'), ), ], ), ); if (confirm == true) { await ref .read(conversationsProvider.notifier) .delete(conv.id); if (mounted && _selectedConvId == conv.id) { setState(() => _selectedConvId = null); } } }, ); }, ), ); }, ); } }