From 9b8acb8b6bd68b26a17cdbf12f789659dbb016f3 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Sat, 15 Oct 2022 09:50:47 +0200 Subject: [PATCH] feat: Enhanced timeline --- lib/pages/chat/chat.dart | 106 +++++++++++----------------- lib/pages/chat/chat_event_list.dart | 10 ++- lib/pages/chat/events/message.dart | 4 +- lib/pages/chat/pinned_events.dart | 2 +- 4 files changed, 50 insertions(+), 72 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 8da2e79d..6e1c73f8 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -184,28 +184,21 @@ class ChatController extends State { setState(() {}); } - Future getTimeline() async { + Future getTimeline({String? eventContextId}) async { if (timeline == null) { + eventContextId ??= room!.fullyRead; await Matrix.of(context).client.roomsLoading; await Matrix.of(context).client.accountDataLoading; - timeline = await room!.getTimeline(onUpdate: updateView); + timeline = await room!.getTimeline( + onUpdate: updateView, + eventContextId: eventContextId, + ); if (timeline!.events.isNotEmpty) { if (room!.markedUnread) room!.markUnread(false); setReadMarker(); } - // when the scroll controller is attached we want to scroll to an event id, if specified - // and update the scroll controller...which will trigger a request history, if the - // "load more" button is visible on the screen - SchedulerBinding.instance.addPostFrameCallback((_) async { - if (mounted) { - final event = VRouter.of(context).queryParameters['event']; - if (event != null) { - scrollToEventId(event); - } - _updateScrollController(); - } - }); + if (eventContextId.isNotEmpty) scrollToEventId(eventContextId); } timeline!.requestKeys(onlineKeyBackupOnly: false); return true; @@ -636,59 +629,42 @@ class ChatController extends State { inputFocus.requestFocus(); } - void scrollToEventId(String eventId) async { - var eventIndex = timeline!.events.indexWhere((e) => e.eventId == eventId); - if (eventIndex == -1) { - // event id not found...maybe we can fetch it? - // the try...finally is here to start and close the loading dialog reliably - await showFutureLoadingDialog( - context: context, - future: () async { - // okay, we first have to fetch if the event is in the room - try { - final event = await timeline!.getEventById(eventId); - if (event == null) { - // event is null...meaning something is off - return; - } - } catch (err) { - if (err is MatrixException && err.errcode == 'M_NOT_FOUND') { - // event wasn't found, as the server gave a 404 or something - return; - } - rethrow; - } - // okay, we know that the event *is* in the room - while (eventIndex == -1) { - if (!canLoadMore) { - // we can't load any more events but still haven't found ours yet...better stop here - return; - } - try { - await timeline!.requestHistory(historyCount: _loadHistoryCount); - } catch (err) { - if (err is TimeoutException) { - // loading the history timed out...so let's do nothing - return; - } - rethrow; - } - eventIndex = - timeline!.events.indexWhere((e) => e.eventId == eventId); - } - }); - } - if (!mounted) { - return; - } - await scrollController.scrollToIndex( - eventIndex, - preferPosition: AutoScrollPosition.middle, - ); - _updateScrollController(); + void openTimelineAtEventId(String eventId) async { + setState(() { + timeline = null; + }); + await getTimeline(eventContextId: eventId); + scrollToEventId(eventId); } - void scrollDown() => scrollController.jumpTo(0); + /// when the scroll controller is attached we want to scroll to an event id, if specified + /// and update the scroll controller...which will trigger a request history, if the + /// "load more" button is visible on the screen + void scrollToEventId(String eventId) async { + SchedulerBinding.instance.addPostFrameCallback((_) async { + if (!mounted) return; + final eventIndex = + timeline!.events.indexWhere((e) => e.eventId == eventId); + if (eventIndex == -1) return; + if (!mounted) return; + await scrollController.scrollToIndex( + eventIndex, + preferPosition: AutoScrollPosition.middle, + ); + _updateScrollController(); + }); + } + + void scrollDown() { + if (timeline!.isFragmentedTimeline) { + setState(() { + timeline = null; + }); + getTimeline(); + } else { + scrollController.jumpTo(0); + } + } void onEmojiSelected(_, Emoji? emoji) { switch (emojiPickerType) { diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 44daa4f9..8e423db0 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -55,16 +55,17 @@ class ChatEventList extends StatelessWidget { ], ); } + i--; // Request history button or progress indicator: - if (i == controller.timeline!.events.length + 1) { + if (i == controller.timeline!.events.length) { if (controller.timeline!.isRequestingHistory) { return const Center( child: CircularProgressIndicator.adaptive(strokeWidth: 2), ); } if (controller.canLoadMore) { - Center( + return Center( child: OutlinedButton( style: OutlinedButton.styleFrom( backgroundColor: Theme.of(context).scaffoldBackgroundColor, @@ -78,7 +79,10 @@ class ChatEventList extends StatelessWidget { } // The message at this index: - final event = controller.timeline!.events[i - 1]; + if (i >= controller.timeline!.events.length) { + return Container(); + } + final event = controller.timeline!.events[i]; return AutoScrollTag( key: ValueKey(event.eventId), diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index b5d5cbc3..801061fe 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -219,9 +219,7 @@ class Message extends StatelessWidget { ); return InkWell( onTap: () { - if (scrollToEventId != null) { - scrollToEventId!(replyEvent.eventId); - } + scrollToEventId?.call(replyEvent.eventId); }, child: AbsorbPointer( child: Container( diff --git a/lib/pages/chat/pinned_events.dart b/lib/pages/chat/pinned_events.dart index 02e72c8f..1b887789 100644 --- a/lib/pages/chat/pinned_events.dart +++ b/lib/pages/chat/pinned_events.dart @@ -35,7 +35,7 @@ class PinnedEvents extends StatelessWidget { )) .toList()); - if (eventId != null) controller.scrollToEventId(eventId); + if (eventId != null) controller.openTimelineAtEventId(eventId); } @override