157 lines
4.4 KiB
Dart
157 lines
4.4 KiB
Dart
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<ChatPage> createState() => _ChatPageState();
|
|
}
|
|
|
|
class _ChatPageState extends ConsumerState<ChatPage> {
|
|
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<Event> 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<String, dynamic>('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;
|
|
}
|