import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; import 'package:pyramid/features/chat/chat_input.dart'; import 'package:pyramid/features/chat/chat_provider.dart'; import 'package:pyramid/features/chat/message_bubble.dart'; class ChatPage extends ConsumerStatefulWidget { final String roomId; const ChatPage({super.key, required this.roomId}); @override ConsumerState createState() => _ChatPageState(); } class _ChatPageState extends ConsumerState { final _scrollCtrl = ScrollController(); Event? _replyTo; String get _roomId => Uri.decodeComponent(widget.roomId); @override void initState() { super.initState(); _scrollCtrl.addListener(_onScroll); } @override void dispose() { _scrollCtrl.dispose(); super.dispose(); } void _onScroll() { if (_scrollCtrl.position.pixels >= _scrollCtrl.position.maxScrollExtent - 300) { final timeline = ref.read(timelineProvider(_roomId)).valueOrNull; timeline?.requestHistory(); } } @override Widget build(BuildContext context) { final room = ref.watch(roomProvider(_roomId)); final timelineAsync = ref.watch(timelineProvider(_roomId)); return Scaffold( appBar: AppBar( leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => context.go('/rooms'), ), title: Text(room?.getLocalizedDisplayname() ?? _roomId), actions: [ IconButton( icon: const Icon(Icons.info_outline), onPressed: () {}, ), ], ), body: Column( children: [ Expanded( child: timelineAsync.when( loading: () => const Center(child: CircularProgressIndicator()), error: (e, _) => Center(child: Text('Fehler: $e')), data: (timeline) => _MessageList( timeline: timeline, scrollCtrl: _scrollCtrl, currentUserId: room?.client.userID ?? '', onReply: (event) => setState(() => _replyTo = event), ), ), ), if (room != null) ChatInput( room: room, replyTo: _replyTo, onClearReply: () => setState(() => _replyTo = null), ), ], ), ); } } class _MessageList extends StatelessWidget { final Timeline timeline; final ScrollController scrollCtrl; final String currentUserId; final ValueChanged onReply; const _MessageList({ required this.timeline, required this.scrollCtrl, required this.currentUserId, required this.onReply, }); @override Widget build(BuildContext context) { final events = timeline.events .where((e) => e.type == EventTypes.Message && e.status != EventStatus.error) .toList(); if (events.isEmpty) { return const Center(child: Text('Noch keine Nachrichten')); } return ListView.builder( controller: scrollCtrl, reverse: true, padding: const EdgeInsets.symmetric(vertical: 8), itemCount: events.length, itemBuilder: (context, i) { final event = events[i]; final isOwn = event.senderId == currentUserId; final showDate = i == events.length - 1 || !_sameDay(event.originServerTs, events[i + 1].originServerTs); final replyEventId = event.content.tryGetMap('m.relates_to') ?['m.in_reply_to']?['event_id'] as String?; final replyEvent = replyEventId != null ? timeline.events.firstWhere( (e) => e.eventId == replyEventId, orElse: () => event, ) : null; return Column( children: [ if (showDate) DateSeparator(date: event.originServerTs), GestureDetector( onLongPress: () => onReply(event), child: MessageBubble( event: event, isOwn: isOwn, replyEvent: replyEvent?.eventId != event.eventId ? replyEvent : null, ), ), ], ); }, ); } bool _sameDay(DateTime a, DateTime b) => a.year == b.year && a.month == b.month && a.day == b.day; }