feat: Use fragmented timeline to jump to event

This commit is contained in:
Krille 2023-03-22 09:16:07 +01:00
parent 2b75a4626d
commit c00a6e3d00
3 changed files with 70 additions and 66 deletions

View file

@ -130,10 +130,6 @@ class ChatController extends State<Chat> {
String pendingText = ''; String pendingText = '';
bool get canLoadMore =>
timeline!.events.isEmpty ||
timeline!.events.last.type != EventTypes.RoomCreate;
bool showEmojiPicker = false; bool showEmojiPicker = false;
void recreateChat() async { void recreateChat() async {
@ -178,7 +174,7 @@ class ChatController extends State<Chat> {
EmojiPickerType emojiPickerType = EmojiPickerType.keyboard; EmojiPickerType emojiPickerType = EmojiPickerType.keyboard;
void requestHistory() async { void requestHistory() async {
if (canLoadMore) { if (!timeline!.canRequestHistory) return;
try { try {
await timeline!.requestHistory(historyCount: _loadHistoryCount); await timeline!.requestHistory(historyCount: _loadHistoryCount);
} catch (err) { } catch (err) {
@ -192,6 +188,21 @@ class ChatController extends State<Chat> {
rethrow; rethrow;
} }
} }
void requestFuture() async {
if (!timeline!.canRequestFuture) return;
try {
await timeline!.requestFuture(historyCount: _loadHistoryCount);
} catch (err) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
(err).toLocalizedString(context),
),
),
);
rethrow;
}
} }
void _updateScrollController() { void _updateScrollController() {
@ -201,13 +212,13 @@ class ChatController extends State<Chat> {
setReadMarker(); setReadMarker();
if (!scrollController.hasClients) return; if (!scrollController.hasClients) return;
if (scrollController.position.pixels == if (scrollController.position.pixels ==
scrollController.position.maxScrollExtent && scrollController.position.maxScrollExtent) {
timeline!.events.isNotEmpty &&
timeline!.events[timeline!.events.length - 1].type !=
EventTypes.RoomCreate) {
requestHistory(); requestHistory();
} else if (scrollController.position.pixels == 0) {
requestFuture();
} }
if (scrollController.position.pixels > 0 && showScrollDownButton == false) { if (timeline?.allowNewEvent == false ||
scrollController.position.pixels > 0 && showScrollDownButton == false) {
setState(() => showScrollDownButton = true); setState(() => showScrollDownButton = true);
} else if (scrollController.position.pixels == 0 && } else if (scrollController.position.pixels == 0 &&
showScrollDownButton == true) { showScrollDownButton == true) {
@ -237,11 +248,14 @@ class ChatController extends State<Chat> {
setState(() {}); setState(() {});
} }
Future<bool> getTimeline() async { Future<void> getTimeline([String? eventContextId]) async {
if (timeline == null) { if (timeline == null) {
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();
@ -261,7 +275,7 @@ class ChatController extends State<Chat> {
}); });
} }
timeline!.requestKeys(onlineKeyBackupOnly: false); timeline!.requestKeys(onlineKeyBackupOnly: false);
return true; return;
} }
Future<void>? _setReadMarkerFuture; Future<void>? _setReadMarkerFuture;
@ -723,45 +737,11 @@ class ChatController extends State<Chat> {
void scrollToEventId(String eventId) async { void scrollToEventId(String eventId) async {
var eventIndex = timeline!.events.indexWhere((e) => e.eventId == eventId); var eventIndex = timeline!.events.indexWhere((e) => e.eventId == eventId);
if (eventIndex == -1) { if (eventIndex == -1) {
// event id not found...maybe we can fetch it? setState(() {
// the try...finally is here to start and close the loading dialog reliably timeline = null;
await showFutureLoadingDialog( });
context: context, await getTimeline(eventId);
future: () async { eventIndex = timeline!.events.indexWhere((e) => e.eventId == eventId);
// 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) { if (!mounted) {
return; return;
@ -773,7 +753,15 @@ class ChatController extends State<Chat> {
_updateScrollController(); _updateScrollController();
} }
void scrollDown() => scrollController.jumpTo(0); void scrollDown() async {
if (!timeline!.allowNewEvent) {
setState(() {
timeline = null;
});
await getTimeline();
}
scrollController.jumpTo(0);
}
void onEmojiSelected(_, Emoji? emoji) { void onEmojiSelected(_, Emoji? emoji) {
switch (emojiPickerType) { switch (emojiPickerType) {

View file

@ -48,6 +48,22 @@ class ChatEventList extends StatelessWidget {
(BuildContext context, int i) { (BuildContext context, int i) {
// Footer to display typing indicator and read receipts: // Footer to display typing indicator and read receipts:
if (i == 0) { if (i == 0) {
if (controller.timeline!.isRequestingFuture) {
return const Center(
child: CircularProgressIndicator.adaptive(strokeWidth: 2),
);
}
if (controller.timeline!.canRequestFuture) {
Center(
child: OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
),
onPressed: controller.requestFuture,
child: Text(L10n.of(context)!.loadMore),
),
);
}
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -64,7 +80,7 @@ class ChatEventList extends StatelessWidget {
child: CircularProgressIndicator.adaptive(strokeWidth: 2), child: CircularProgressIndicator.adaptive(strokeWidth: 2),
); );
} }
if (controller.canLoadMore) { if (controller.timeline!.canRequestHistory) {
Center( Center(
child: OutlinedButton( child: OutlinedButton(
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(

View file

@ -179,7 +179,7 @@ class ChatView extends StatelessWidget {
child: StreamBuilder( child: StreamBuilder(
stream: controller.room!.onUpdate.stream stream: controller.room!.onUpdate.stream
.rateLimit(const Duration(seconds: 1)), .rateLimit(const Duration(seconds: 1)),
builder: (context, snapshot) => FutureBuilder<bool>( builder: (context, snapshot) => FutureBuilder(
future: controller.getTimeline(), future: controller.getTimeline(),
builder: (BuildContext context, snapshot) { builder: (BuildContext context, snapshot) {
return Scaffold( return Scaffold(