From 7f0bd867ec63a425d9252670e50de64d229b2974 Mon Sep 17 00:00:00 2001 From: Krille Fear Date: Thu, 17 Feb 2022 21:12:47 +0100 Subject: [PATCH] fix: Set read marker only on user interaction --- lib/pages/chat/chat.dart | 19 +- lib/pages/chat/chat_view.dart | 546 +++++++++++++++++----------------- 2 files changed, 289 insertions(+), 276 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index a3ad8cc0c..9adf24af2 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -147,6 +147,7 @@ class ChatController extends State { if (!mounted) { return; } + setReadMarker(); if (scrollController.position.pixels == scrollController.position.maxScrollExtent && timeline!.events.isNotEmpty && @@ -201,8 +202,8 @@ class ChatController extends State { if (timeline == null) { timeline = await room!.getTimeline(onUpdate: updateView); if (timeline!.events.isNotEmpty) { - // ignore: unawaited_futures if (room!.markedUnread) room!.markUnread(false); + setReadMarker(); } // when the scroll controller is attached we want to scroll to an event id, if specified @@ -220,15 +221,24 @@ class ChatController extends State { } filteredEvents = timeline!.getFilteredEvents(unfolded: unfolded); timeline!.requestKeys(); - if ((room!.hasNewMessages || room!.notificationCount > 0) && + return true; + } + + Future? _setReadMarkerFuture; + + void setReadMarker([_]) { + if (_setReadMarkerFuture == null && + (room!.hasNewMessages || room!.notificationCount > 0) && timeline != null && timeline!.events.isNotEmpty && Matrix.of(context).webHasFocus) { + Logs().v('Set read marker...'); // ignore: unawaited_futures - timeline!.setReadMarker(); + _setReadMarkerFuture = timeline!.setReadMarker().then((_) { + _setReadMarkerFuture = null; + }); room!.client.updateIosBadge(); } - return true; } @override @@ -900,6 +910,7 @@ class ChatController extends State { } void onInputBarChanged(String text) { + setReadMarker(); if (text.endsWith(' ') && matrix!.hasComplexBundles) { final clients = currentRoomBundle; for (final client in clients) { diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index b443f90b7..094847505 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -156,295 +156,297 @@ class ChatView extends StatelessWidget { redirector.stopRedirection(); } }, - child: StreamBuilder( - stream: controller.room!.onUpdate.stream - .rateLimit(const Duration(milliseconds: 250)), - builder: (context, snapshot) => FutureBuilder( - future: controller.getTimeline(), - builder: (BuildContext context, snapshot) { - return Scaffold( - appBar: AppBar( - actionsIconTheme: IconThemeData( - color: controller.selectedEvents.isEmpty - ? null - : Theme.of(context).colorScheme.primary, + child: GestureDetector( + onTapDown: controller.setReadMarker, + child: StreamBuilder( + stream: controller.room!.onUpdate.stream + .rateLimit(const Duration(milliseconds: 250)), + builder: (context, snapshot) => FutureBuilder( + future: controller.getTimeline(), + builder: (BuildContext context, snapshot) { + return Scaffold( + appBar: AppBar( + actionsIconTheme: IconThemeData( + color: controller.selectedEvents.isEmpty + ? null + : Theme.of(context).colorScheme.primary, + ), + leading: controller.selectMode + ? IconButton( + icon: const Icon(Icons.close), + onPressed: controller.clearSelectedEvents, + tooltip: L10n.of(context)!.close, + color: Theme.of(context).colorScheme.primary, + ) + : UnreadBadgeBackButton(roomId: controller.roomId!), + titleSpacing: 0, + title: ChatAppBarTitle(controller), + actions: _appBarActions(context), ), - leading: controller.selectMode - ? IconButton( - icon: const Icon(Icons.close), - onPressed: controller.clearSelectedEvents, - tooltip: L10n.of(context)!.close, - color: Theme.of(context).colorScheme.primary, + floatingActionButton: controller.showScrollDownButton && + controller.selectedEvents.isEmpty + ? Padding( + padding: const EdgeInsets.only(bottom: 56.0), + child: FloatingActionButton( + onPressed: controller.scrollDown, + foregroundColor: + Theme.of(context).textTheme.bodyText2!.color, + backgroundColor: + Theme.of(context).scaffoldBackgroundColor, + mini: true, + child: Icon(Icons.arrow_downward_outlined, + color: Theme.of(context).primaryColor), + ), ) - : UnreadBadgeBackButton(roomId: controller.roomId!), - titleSpacing: 0, - title: ChatAppBarTitle(controller), - actions: _appBarActions(context), - ), - floatingActionButton: controller.showScrollDownButton && - controller.selectedEvents.isEmpty - ? Padding( - padding: const EdgeInsets.only(bottom: 56.0), - child: FloatingActionButton( - onPressed: controller.scrollDown, - foregroundColor: - Theme.of(context).textTheme.bodyText2!.color, - backgroundColor: - Theme.of(context).scaffoldBackgroundColor, - mini: true, - child: Icon(Icons.arrow_downward_outlined, - color: Theme.of(context).primaryColor), - ), - ) - : null, - backgroundColor: Theme.of(context).colorScheme.surface, - body: DropTarget( - onDragDone: controller.onDragDone, - onDragEntered: controller.onDragEntered, - onDragExited: controller.onDragExited, - child: Stack( - children: [ - if (Matrix.of(context).wallpaper != null) - Image.file( - Matrix.of(context).wallpaper!, - width: double.infinity, - height: double.infinity, - fit: BoxFit.cover, - ), - SafeArea( - child: Column( - children: [ - TombstoneDisplay(controller), - PinnedEvents(controller), - Expanded( - child: GestureDetector( - onTap: controller.clearSingleSelectedEvent, - child: Builder( - builder: (context) { - if (snapshot.hasError) { - SentryController.captureException( - snapshot.error, - StackTrace.current, - ); - } - if (controller.timeline == null) { - return const Center( - child: - CircularProgressIndicator.adaptive( - strokeWidth: 2), - ); - } + : null, + backgroundColor: Theme.of(context).colorScheme.surface, + body: DropTarget( + onDragDone: controller.onDragDone, + onDragEntered: controller.onDragEntered, + onDragExited: controller.onDragExited, + child: Stack( + children: [ + if (Matrix.of(context).wallpaper != null) + Image.file( + Matrix.of(context).wallpaper!, + width: double.infinity, + height: double.infinity, + fit: BoxFit.cover, + ), + SafeArea( + child: Column( + children: [ + TombstoneDisplay(controller), + PinnedEvents(controller), + Expanded( + child: GestureDetector( + onTap: controller.clearSingleSelectedEvent, + child: Builder( + builder: (context) { + if (snapshot.hasError) { + SentryController.captureException( + snapshot.error, + StackTrace.current, + ); + } + if (controller.timeline == null) { + return const Center( + child: CircularProgressIndicator + .adaptive(strokeWidth: 2), + ); + } - // create a map of eventId --> index to greatly improve performance of - // ListView's findChildIndexCallback - final thisEventsKeyMap = {}; - for (var i = 0; - i < controller.filteredEvents.length; - i++) { - thisEventsKeyMap[controller - .filteredEvents[i].eventId] = i; - } - return ListView.custom( - padding: EdgeInsets.only( - top: 16, - bottom: 4, - left: horizontalPadding, - right: horizontalPadding, - ), - reverse: true, - controller: controller.scrollController, - keyboardDismissBehavior: PlatformInfos - .isIOS - ? ScrollViewKeyboardDismissBehavior - .onDrag - : ScrollViewKeyboardDismissBehavior - .manual, - childrenDelegate: - SliverChildBuilderDelegate( - (BuildContext context, int i) { - return i == - controller.filteredEvents - .length + - 1 - ? controller.timeline! - .isRequestingHistory - ? const Center( - child: - CircularProgressIndicator - .adaptive( - strokeWidth: - 2), - ) - : controller.canLoadMore - ? Center( - child: OutlinedButton( - style: - OutlinedButton - .styleFrom( - backgroundColor: - Theme.of( - context) - .scaffoldBackgroundColor, + // create a map of eventId --> index to greatly improve performance of + // ListView's findChildIndexCallback + final thisEventsKeyMap = {}; + for (var i = 0; + i < controller.filteredEvents.length; + i++) { + thisEventsKeyMap[controller + .filteredEvents[i].eventId] = i; + } + return ListView.custom( + padding: EdgeInsets.only( + top: 16, + bottom: 4, + left: horizontalPadding, + right: horizontalPadding, + ), + reverse: true, + controller: controller.scrollController, + keyboardDismissBehavior: PlatformInfos + .isIOS + ? ScrollViewKeyboardDismissBehavior + .onDrag + : ScrollViewKeyboardDismissBehavior + .manual, + childrenDelegate: + SliverChildBuilderDelegate( + (BuildContext context, int i) { + return i == + controller.filteredEvents + .length + + 1 + ? controller.timeline! + .isRequestingHistory + ? const Center( + child: + CircularProgressIndicator + .adaptive( + strokeWidth: + 2), + ) + : controller.canLoadMore + ? Center( + child: + OutlinedButton( + style: + OutlinedButton + .styleFrom( + backgroundColor: + Theme.of( + context) + .scaffoldBackgroundColor, + ), + onPressed: controller + .requestHistory, + child: Text(L10n.of( + context)! + .loadMore), ), - onPressed: controller - .requestHistory, - child: Text(L10n.of( - context)! - .loadMore), - ), - ) - : Container() - : i == 0 - ? Column( - mainAxisSize: - MainAxisSize.min, - children: [ - SeenByRow(controller), - TypingIndicators( - controller), - ], - ) - : AutoScrollTag( - key: ValueKey(controller - .filteredEvents[i - 1] - .eventId), - index: i - 1, - controller: controller - .scrollController, - child: Swipeable( + ) + : Container() + : i == 0 + ? Column( + mainAxisSize: + MainAxisSize.min, + children: [ + SeenByRow(controller), + TypingIndicators( + controller), + ], + ) + : AutoScrollTag( key: ValueKey(controller .filteredEvents[ i - 1] .eventId), - background: - const Padding( - padding: EdgeInsets - .symmetric( - horizontal: - 12.0), - child: Center( - child: Icon(Icons - .reply_outlined), + index: i - 1, + controller: controller + .scrollController, + child: Swipeable( + key: ValueKey(controller + .filteredEvents[ + i - 1] + .eventId), + background: + const Padding( + padding: EdgeInsets + .symmetric( + horizontal: + 12.0), + child: Center( + child: Icon(Icons + .reply_outlined), + ), ), - ), - direction: - SwipeDirection - .endToStart, - onSwipe: (direction) => - controller.replyAction( - replyTo: controller - .filteredEvents[ - i - 1]), - child: Message( - controller.filteredEvents[ - i - 1], - onInfoTab: controller - .showEventInfo, - onAvatarTab: - (Event event) => - showModalBottomSheet( - context: + direction: + SwipeDirection + .endToStart, + onSwipe: (direction) => + controller.replyAction( + replyTo: controller + .filteredEvents[ + i - 1]), + child: Message( + controller.filteredEvents[ + i - 1], + onInfoTab: controller + .showEventInfo, + onAvatarTab: (Event event) => + showModalBottomSheet( + context: + context, + builder: (c) => + UserBottomSheet( + user: event + .sender, + outerContext: context, - builder: - (c) => - UserBottomSheet( - user: event - .sender, - outerContext: - context, - onMention: () => controller - .sendController - .text += '${event.sender.mention} ', - ), + onMention: () => controller + .sendController + .text += '${event.sender.mention} ', ), - unfold: controller - .unfold, - onSelect: controller - .onSelectMessage, - scrollToEventId: - (String eventId) => - controller.scrollToEventId( - eventId), - longPressSelect: - controller - .selectedEvents - .isEmpty, - selected: controller - .selectedEvents - .any((e) => e.eventId == controller.filteredEvents[i - 1].eventId), - timeline: controller.timeline!, - nextEvent: i < controller.filteredEvents.length ? controller.filteredEvents[i] : null), - ), - ); - }, - childCount: - controller.filteredEvents.length + - 2, - findChildIndexCallback: (key) => - controller.findChildIndexCallback( - key, thisEventsKeyMap), - ), - ); - }, - )), - ), - if (controller.room!.canSendDefaultMessages && - controller.room!.membership == Membership.join) - Container( - margin: EdgeInsets.only( - bottom: bottomSheetPadding, - left: bottomSheetPadding, - right: bottomSheetPadding, - ), - constraints: const BoxConstraints( - maxWidth: FluffyThemes.columnWidth * 2.5), - alignment: Alignment.center, - child: Material( - borderRadius: const BorderRadius.only( - bottomLeft: - Radius.circular(AppConfig.borderRadius), - bottomRight: - Radius.circular(AppConfig.borderRadius), + ), + unfold: controller + .unfold, + onSelect: controller + .onSelectMessage, + scrollToEventId: + (String eventId) => + controller.scrollToEventId( + eventId), + longPressSelect: + controller + .selectedEvents + .isEmpty, + selected: controller + .selectedEvents + .any((e) => e.eventId == controller.filteredEvents[i - 1].eventId), + timeline: controller.timeline!, + nextEvent: i < controller.filteredEvents.length ? controller.filteredEvents[i] : null), + ), + ); + }, + childCount: + controller.filteredEvents.length + + 2, + findChildIndexCallback: (key) => + controller.findChildIndexCallback( + key, thisEventsKeyMap), + ), + ); + }, + )), + ), + if (controller.room!.canSendDefaultMessages && + controller.room!.membership == Membership.join) + Container( + margin: EdgeInsets.only( + bottom: bottomSheetPadding, + left: bottomSheetPadding, + right: bottomSheetPadding, ), - elevation: 6, - shadowColor: Theme.of(context) - .secondaryHeaderColor - .withAlpha(100), - clipBehavior: Clip.hardEdge, - color: Theme.of(context) - .appBarTheme - .backgroundColor, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const ConnectionStatusHeader(), - ReactionsPicker(controller), - ReplyDisplay(controller), - ChatInputRow(controller), - ChatEmojiPicker(controller), - ], + constraints: const BoxConstraints( + maxWidth: FluffyThemes.columnWidth * 2.5), + alignment: Alignment.center, + child: Material( + borderRadius: const BorderRadius.only( + bottomLeft: + Radius.circular(AppConfig.borderRadius), + bottomRight: + Radius.circular(AppConfig.borderRadius), + ), + elevation: 6, + shadowColor: Theme.of(context) + .secondaryHeaderColor + .withAlpha(100), + clipBehavior: Clip.hardEdge, + color: Theme.of(context) + .appBarTheme + .backgroundColor, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const ConnectionStatusHeader(), + ReactionsPicker(controller), + ReplyDisplay(controller), + ChatInputRow(controller), + ChatEmojiPicker(controller), + ], + ), ), ), - ), - ], - ), - ), - if (controller.dragging) - Container( - color: Theme.of(context) - .scaffoldBackgroundColor - .withOpacity(0.9), - alignment: Alignment.center, - child: const Icon( - Icons.upload_outlined, - size: 100, + ], ), ), - ], + if (controller.dragging) + Container( + color: Theme.of(context) + .scaffoldBackgroundColor + .withOpacity(0.9), + alignment: Alignment.center, + child: const Icon( + Icons.upload_outlined, + size: 100, + ), + ), + ], + ), ), - ), - ); - }, + ); + }, + ), ), ), );