// Version: 1.0.0 | Created: 2026-04-01 // Full chat screen — timeline + message input. import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/network/matrix_client.dart'; import 'chat_controller.dart'; import 'message_bubble.dart'; import 'message_input.dart'; class ChatScreen extends ConsumerWidget { const ChatScreen({super.key, required this.roomId}); final String roomId; @override Widget build(BuildContext context, WidgetRef ref) { // Decode the roomId — GoRouter encodes ! as %21 etc. final decodedRoomId = Uri.decodeComponent(roomId); final client = ref.watch(matrixClientProvider); final room = client.getRoomById(decodedRoomId); final roomName = room?.getLocalizedDisplayname() ?? 'Chat'; final roomAvatar = room?.avatar?.toString(); return Scaffold( appBar: AppBar( titleSpacing: 0, title: Row( children: [ _RoomAvatarSmall(name: roomName, avatarUrl: roomAvatar), const SizedBox(width: 10), Flexible(child: Text(roomName, overflow: TextOverflow.ellipsis)), ], ), actions: [ IconButton( icon: const Icon(Icons.call), tooltip: 'Start call (Phase 2)', onPressed: null, // Phase 2 ), IconButton( icon: const Icon(Icons.more_vert), tooltip: 'Room options', onPressed: () { // Phase 2: room settings sheet }, ), ], ), body: Column( children: [ Expanded(child: _Timeline(roomId: decodedRoomId)), _Input(roomId: decodedRoomId), ], ), ); } } class _RoomAvatarSmall extends StatelessWidget { const _RoomAvatarSmall({required this.name, this.avatarUrl}); final String name; final String? avatarUrl; @override Widget build(BuildContext context) { final initials = name.isNotEmpty ? name[0].toUpperCase() : '?'; if (avatarUrl != null) { return CircleAvatar( radius: 18, backgroundImage: NetworkImage(avatarUrl!), ); } return CircleAvatar( radius: 18, backgroundColor: Theme.of(context).colorScheme.primary.withAlpha(51), child: Text( initials, style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.primary, ), ), ); } } class _Timeline extends ConsumerWidget { const _Timeline({required this.roomId}); final String roomId; @override Widget build(BuildContext context, WidgetRef ref) { final timelineAsync = ref.watch(chatTimelineProvider(roomId)); return timelineAsync.when( loading: () => const Center(child: CircularProgressIndicator()), error: (error, _) => Center( child: Padding( padding: const EdgeInsets.all(24), child: Text('Could not load messages: $error'), ), ), data: (messages) { if (messages.isEmpty) { return Center( child: Text( 'No messages yet. Say hello!', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onSurface.withAlpha(102), ), ), ); } return ListView.builder( reverse: true, padding: const EdgeInsets.symmetric(vertical: 8), itemCount: messages.length, itemBuilder: (context, index) { return MessageBubble(message: messages[index]); }, ); }, ); } } class _Input extends ConsumerWidget { const _Input({required this.roomId}); final String roomId; @override Widget build(BuildContext context, WidgetRef ref) { final isSending = ref.watch(sendMessageProvider); return MessageInput( isSending: isSending, onSend: (text) async { final error = await ref .read(sendMessageProvider.notifier) .send(roomId, text); if (error != null && context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Failed to send message: $error'), backgroundColor: Theme.of(context).colorScheme.error, ), ); } }, ); } }