feat: Enhanced timeline

This commit is contained in:
Christian Pauly 2022-10-15 09:50:47 +02:00
parent c365469dc5
commit 9b8acb8b6b
4 changed files with 50 additions and 72 deletions

View File

@ -184,28 +184,21 @@ class ChatController extends State<Chat> {
setState(() {}); setState(() {});
} }
Future<bool> getTimeline() async { Future<bool> getTimeline({String? eventContextId}) async {
if (timeline == null) { if (timeline == null) {
eventContextId ??= room!.fullyRead;
await Matrix.of(context).client.roomsLoading; await Matrix.of(context).client.roomsLoading;
await Matrix.of(context).client.accountDataLoading; 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 (timeline!.events.isNotEmpty) {
if (room!.markedUnread) room!.markUnread(false); if (room!.markedUnread) room!.markUnread(false);
setReadMarker(); setReadMarker();
} }
// when the scroll controller is attached we want to scroll to an event id, if specified if (eventContextId.isNotEmpty) scrollToEventId(eventContextId);
// 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();
}
});
} }
timeline!.requestKeys(onlineKeyBackupOnly: false); timeline!.requestKeys(onlineKeyBackupOnly: false);
return true; return true;
@ -636,59 +629,42 @@ class ChatController extends State<Chat> {
inputFocus.requestFocus(); inputFocus.requestFocus();
} }
void scrollToEventId(String eventId) async { void openTimelineAtEventId(String eventId) async {
var eventIndex = timeline!.events.indexWhere((e) => e.eventId == eventId); setState(() {
if (eventIndex == -1) { timeline = null;
// event id not found...maybe we can fetch it? });
// the try...finally is here to start and close the loading dialog reliably await getTimeline(eventContextId: eventId);
await showFutureLoadingDialog( scrollToEventId(eventId);
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 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) { void onEmojiSelected(_, Emoji? emoji) {
switch (emojiPickerType) { switch (emojiPickerType) {

View File

@ -55,16 +55,17 @@ class ChatEventList extends StatelessWidget {
], ],
); );
} }
i--;
// Request history button or progress indicator: // Request history button or progress indicator:
if (i == controller.timeline!.events.length + 1) { if (i == controller.timeline!.events.length) {
if (controller.timeline!.isRequestingHistory) { if (controller.timeline!.isRequestingHistory) {
return const Center( return const Center(
child: CircularProgressIndicator.adaptive(strokeWidth: 2), child: CircularProgressIndicator.adaptive(strokeWidth: 2),
); );
} }
if (controller.canLoadMore) { if (controller.canLoadMore) {
Center( return Center(
child: OutlinedButton( child: OutlinedButton(
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
backgroundColor: Theme.of(context).scaffoldBackgroundColor, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
@ -78,7 +79,10 @@ class ChatEventList extends StatelessWidget {
} }
// The message at this index: // 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( return AutoScrollTag(
key: ValueKey(event.eventId), key: ValueKey(event.eventId),

View File

@ -219,9 +219,7 @@ class Message extends StatelessWidget {
); );
return InkWell( return InkWell(
onTap: () { onTap: () {
if (scrollToEventId != null) { scrollToEventId?.call(replyEvent.eventId);
scrollToEventId!(replyEvent.eventId);
}
}, },
child: AbsorbPointer( child: AbsorbPointer(
child: Container( child: Container(

View File

@ -35,7 +35,7 @@ class PinnedEvents extends StatelessWidget {
)) ))
.toList()); .toList());
if (eventId != null) controller.scrollToEventId(eventId); if (eventId != null) controller.openTimelineAtEventId(eventId);
} }
@override @override