feat: better scroll to last read message handling

This commit is contained in:
krille-chan 2023-10-14 09:17:00 +02:00
parent 89493f364a
commit 9b21c0d951
No known key found for this signature in database
2 changed files with 75 additions and 31 deletions

View file

@ -297,13 +297,39 @@ class ChatController extends State<ChatPageWithRoom> {
_loadDraft();
super.initState();
sendingClient = Matrix.of(context).client;
readMarkerEventId = room.fullyRead;
loadTimelineFuture =
_getTimeline(eventContextId: readMarkerEventId).onError(
ErrorReporter(context, 'Unable to load timeline').onErrorCallback,
);
_tryLoadTimeline();
}
void _tryLoadTimeline() async {
loadTimelineFuture = _getTimeline();
try {
await loadTimelineFuture;
final fullyRead = room.fullyRead;
if (fullyRead.isEmpty) return;
if (timeline!.events.any((event) => event.eventId == fullyRead)) {
Logs().v('Scroll up to visible event', fullyRead);
scrollToEventId(fullyRead);
setReadMarker();
return;
}
if (!mounted) return;
_showScrollUpMaterialBanner(fullyRead);
} catch (e, s) {
ErrorReporter(context, 'Unable to load timeline').onErrorCallback(e, s);
rethrow;
}
}
String? scrollUpBannerEventId;
void discardScrollUpBannerEventId() => setState(() {
scrollUpBannerEventId = null;
});
void _showScrollUpMaterialBanner(String eventId) => setState(() {
scrollUpBannerEventId = eventId;
});
void updateView() {
if (!mounted) return;
setState(() {});
@ -313,7 +339,6 @@ class ChatController extends State<ChatPageWithRoom> {
Future<void> _getTimeline({
String? eventContextId,
Duration timeout = const Duration(seconds: 7),
}) async {
await Matrix.of(context).client.roomsLoading;
await Matrix.of(context).client.accountDataLoading;
@ -322,34 +347,21 @@ class ChatController extends State<ChatPageWithRoom> {
eventContextId = null;
}
try {
timeline = await room
.getTimeline(
onUpdate: updateView,
eventContextId: eventContextId,
)
.timeout(timeout);
timeline = await room.getTimeline(
onUpdate: updateView,
eventContextId: eventContextId,
);
} catch (e, s) {
Logs().w('Unable to load timeline on event ID $eventContextId', e, s);
if (!mounted) return;
timeline = await room.getTimeline(onUpdate: updateView);
if (!mounted) return;
if (e is TimeoutException || e is IOException) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(L10n.of(context)!.jumpToLastReadMessage),
action: SnackBarAction(
label: L10n.of(context)!.jump,
onPressed: () => scrollToEventId(eventContextId!),
),
),
);
_showScrollUpMaterialBanner(eventContextId!);
}
}
timeline!.requestKeys(onlineKeyBackupOnly: false);
if (timeline!.events.isNotEmpty) {
if (room.markedUnread) room.markUnread(false);
setReadMarker();
}
if (room.markedUnread) room.markUnread(false);
// 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
@ -370,6 +382,7 @@ class ChatController extends State<ChatPageWithRoom> {
void setReadMarker({String? eventId}) {
if (_setReadMarkerFuture != null) return;
if (scrollUpBannerEventId != null) return;
if (eventId == null &&
!room.hasNewMessages &&
room.notificationCount == 0) {
@ -380,7 +393,7 @@ class ChatController extends State<ChatPageWithRoom> {
final timeline = this.timeline;
if (timeline == null || timeline.events.isEmpty) return;
Logs().v('Set read marker...', eventId);
Logs().d('Set read marker...', eventId);
// ignore: unawaited_futures
_setReadMarkerFuture = timeline.setReadMarker(eventId: eventId).then((_) {
_setReadMarkerFuture = null;
@ -886,10 +899,7 @@ class ChatController extends State<ChatPageWithRoom> {
setState(() {
timeline = null;
_scrolledUp = false;
loadTimelineFuture = _getTimeline(
eventContextId: eventId,
timeout: const Duration(seconds: 30),
).onError(
loadTimelineFuture = _getTimeline(eventContextId: eventId).onError(
ErrorReporter(context, 'Unable to load timeline after scroll to ID')
.onErrorCallback,
);
@ -902,7 +912,7 @@ class ChatController extends State<ChatPageWithRoom> {
}
await scrollController.scrollToIndex(
eventIndex,
preferPosition: AutoScrollPosition.middle,
preferPosition: AutoScrollPosition.end,
);
_updateScrollController();
}

View file

@ -146,6 +146,7 @@ class ChatView extends StatelessWidget {
);
}
final bottomSheetPadding = FluffyThemes.isColumnMode(context) ? 16.0 : 8.0;
final scrollUpBannerEventId = controller.scrollUpBannerEventId;
return WillPopScope(
onWillPop: () async {
@ -220,6 +221,39 @@ class ChatView extends StatelessWidget {
child: Column(
children: <Widget>[
TombstoneDisplay(controller),
if (scrollUpBannerEventId != null)
Material(
color: Theme.of(context)
.colorScheme
.secondaryContainer,
child: ListTile(
leading: IconButton(
color: Theme.of(context)
.colorScheme
.onSurfaceVariant,
icon: const Icon(Icons.close),
tooltip: L10n.of(context)!.close,
onPressed: () {
controller.discardScrollUpBannerEventId();
controller.setReadMarker();
},
),
title: Text(
L10n.of(context)!.jumpToLastReadMessage,
),
contentPadding:
const EdgeInsets.only(left: 8),
trailing: TextButton(
onPressed: () {
controller.scrollToEventId(
scrollUpBannerEventId,
);
controller.discardScrollUpBannerEventId();
},
child: Text(L10n.of(context)!.jump),
),
),
),
PinnedEvents(controller),
Expanded(
child: GestureDetector(