From 57db6c88cdb39a859d335556c6476fc393e6156b Mon Sep 17 00:00:00 2001 From: Krille Date: Sun, 28 Jul 2024 09:49:05 +0200 Subject: [PATCH] refactor: Migrate to scrollabel list --- lib/pages/chat/chat.dart | 44 ++++--- lib/pages/chat/chat_event_list.dart | 190 +++++++++++++--------------- pubspec.lock | 8 +- pubspec.yaml | 4 +- 4 files changed, 123 insertions(+), 123 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 2592f560c..be826704c 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -16,7 +16,7 @@ import 'package:go_router/go_router.dart'; import 'package:image_picker/image_picker.dart'; import 'package:matrix/matrix.dart'; import 'package:record/record.dart'; -import 'package:scroll_to_index/scroll_to_index.dart'; +import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:universal_html/html.dart' as html; @@ -29,6 +29,7 @@ import 'package:fluffychat/pages/chat/recording_dialog.dart'; import 'package:fluffychat/pages/chat_details/chat_details.dart'; import 'package:fluffychat/utils/error_reporter.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/app_lock.dart'; @@ -104,7 +105,13 @@ class ChatController extends State String get roomId => widget.room.id; - final AutoScrollController scrollController = AutoScrollController(); + final ItemScrollController itemScrollController = ItemScrollController(); + final ScrollOffsetController scrollOffsetController = + ScrollOffsetController(); + final ItemPositionsListener itemPositionsListener = + ItemPositionsListener.create(); + final ScrollOffsetListener scrollOffsetListener = + ScrollOffsetListener.create(); FocusNode inputFocus = FocusNode(); StreamSubscription? onFocusSub; @@ -230,21 +237,20 @@ class ChatController extends State setReadMarker(eventId: mostRecentEventId); } - void _updateScrollController() { + void _updateScrollController(double offset) { if (!mounted) { return; } - if (!scrollController.hasClients) return; + if (timeline?.allowNewEvent == false || - scrollController.position.pixels > 0 && _scrolledUp == false) { + offset > 2 && _scrolledUp == false) { setState(() => _scrolledUp = true); - } else if (scrollController.position.pixels <= 0 && _scrolledUp == true) { + } else if (offset <= 2 && _scrolledUp == true) { setState(() => _scrolledUp = false); setReadMarker(); } - if (scrollController.position.pixels == 0 || - scrollController.position.pixels == 64) { + if (offset == 0 || offset == 64) { requestFuture(); } } @@ -259,7 +265,7 @@ class ChatController extends State @override void initState() { - scrollController.addListener(_updateScrollController); + scrollOffsetListener.changes.listen(_updateScrollController); inputFocus.addListener(_inputFocusListener); _loadDraft(); @@ -292,7 +298,7 @@ class ChatController extends State if (timeline?.events.any((event) => event.eventId == fullyRead) ?? false) { Logs().v('Scroll up to visible event', fullyRead); - setReadMarker(); + scrollToEventId(fullyRead); return; } if (!mounted) return; @@ -901,7 +907,9 @@ class ChatController extends State } void scrollToEventId(String eventId) async { - final eventIndex = timeline!.events.indexWhere((e) => e.eventId == eventId); + final events = + timeline!.events.where((event) => event.isVisibleInGui).toList(); + final eventIndex = events.indexWhere((e) => e.eventId == eventId); if (eventIndex == -1) { setState(() { timeline = null; @@ -920,11 +928,11 @@ class ChatController extends State setState(() { scrollToEventIdMarker = eventId; }); - await scrollController.scrollToIndex( - eventIndex, - preferPosition: AutoScrollPosition.middle, + await itemScrollController.scrollTo( + index: eventIndex + 1, + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, ); - _updateScrollController(); } void scrollDown() async { @@ -939,7 +947,11 @@ class ChatController extends State }); await loadTimelineFuture; } - scrollController.jumpTo(0); + itemScrollController.scrollTo( + index: 0, + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + ); } void onEmojiSelected(_, Emoji? emoji) { diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 2d56aa5c0..37fa9bd3d 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; -import 'package:scroll_to_index/scroll_to_index.dart'; +import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; @@ -12,7 +12,6 @@ import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart'; import 'package:fluffychat/utils/account_config.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart'; -import 'package:fluffychat/utils/platform_infos.dart'; class ChatEventList extends StatelessWidget { final ChatController controller; @@ -41,119 +40,108 @@ class ChatEventList extends StatelessWidget { controller.room.client.applicationAccountConfig.wallpaperUrl != null; return SelectionArea( - child: ListView.custom( + child: ScrollablePositionedList.builder( padding: EdgeInsets.only( top: 16, bottom: 8, left: horizontalPadding, right: horizontalPadding, ), + itemCount: events.length + 2, reverse: true, - controller: controller.scrollController, - keyboardDismissBehavior: PlatformInfos.isIOS - ? ScrollViewKeyboardDismissBehavior.onDrag - : ScrollViewKeyboardDismissBehavior.manual, - childrenDelegate: SliverChildBuilderDelegate( - (BuildContext context, int i) { - // Footer to display typing indicator and read receipts: - if (i == 0) { - if (controller.timeline!.isRequestingFuture) { - return const Center( - child: CircularProgressIndicator.adaptive(strokeWidth: 2), - ); - } - if (controller.timeline!.canRequestFuture) { - return Center( - child: IconButton( - onPressed: controller.requestFuture, - icon: const Icon(Icons.refresh_outlined), - ), - ); - } - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - SeenByRow(controller), - TypingIndicators(controller), - ], + itemScrollController: controller.itemScrollController, + scrollOffsetController: controller.scrollOffsetController, + itemPositionsListener: controller.itemPositionsListener, + scrollOffsetListener: controller.scrollOffsetListener, + itemBuilder: (BuildContext context, int i) { + // Footer to display typing indicator and read receipts: + if (i == 0) { + if (controller.timeline!.isRequestingFuture) { + return const Center( + child: CircularProgressIndicator.adaptive(strokeWidth: 2), ); } + if (controller.timeline!.canRequestFuture) { + return Center( + child: IconButton( + onPressed: controller.requestFuture, + icon: const Icon(Icons.refresh_outlined), + ), + ); + } + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SeenByRow(controller), + TypingIndicators(controller), + ], + ); + } - // Request history button or progress indicator: - if (i == events.length + 1) { - if (controller.timeline!.isRequestingHistory) { - return const Center( - child: CircularProgressIndicator.adaptive(strokeWidth: 2), - ); - } - if (controller.timeline!.canRequestHistory) { - return Builder( - builder: (context) { - WidgetsBinding.instance - .addPostFrameCallback(controller.requestHistory); - return Center( - child: IconButton( - onPressed: controller.requestHistory, - icon: const Icon(Icons.refresh_outlined), - ), - ); - }, - ); - } - return const SizedBox.shrink(); + // Request history button or progress indicator: + if (i == events.length + 1) { + if (controller.timeline!.isRequestingHistory) { + return const Center( + child: CircularProgressIndicator.adaptive(strokeWidth: 2), + ); + } + if (controller.timeline!.canRequestHistory) { + return Builder( + builder: (context) { + WidgetsBinding.instance + .addPostFrameCallback(controller.requestHistory); + return Center( + child: IconButton( + onPressed: controller.requestHistory, + icon: const Icon(Icons.refresh_outlined), + ), + ); + }, + ); } - i--; + return const SizedBox.shrink(); + } + i--; - // The message at this index: - final event = events[i]; - final animateIn = animateInEventIndex != null && - controller.timeline!.events.length > animateInEventIndex && - event == controller.timeline!.events[animateInEventIndex]; + // The message at this index: + final event = events[i]; + final animateIn = animateInEventIndex != null && + controller.timeline!.events.length > animateInEventIndex && + event == controller.timeline!.events[animateInEventIndex]; - return AutoScrollTag( - key: ValueKey(event.eventId), - index: i, - controller: controller.scrollController, - child: Message( - event, - animateIn: animateIn, - resetAnimateIn: () { - controller.animateInEventIndex = null; - }, - onSwipe: () => controller.replyAction(replyTo: event), - onInfoTab: controller.showEventInfo, - onAvatarTab: (Event event) => showAdaptiveBottomSheet( - context: context, - builder: (c) => UserBottomSheet( - user: event.senderFromMemoryOrFallback, - outerContext: context, - onMention: () => controller.sendController.text += - '${event.senderFromMemoryOrFallback.mention} ', - ), - ), - highlightMarker: - controller.scrollToEventIdMarker == event.eventId, - onSelect: controller.onSelectMessage, - scrollToEventId: (String eventId) => - controller.scrollToEventId(eventId), - longPressSelect: controller.selectedEvents.isNotEmpty, - selected: controller.selectedEvents - .any((e) => e.eventId == event.eventId), - timeline: controller.timeline!, - displayReadMarker: - controller.readMarkerEventId == event.eventId && - controller.timeline?.allowNewEvent == false, - nextEvent: i + 1 < events.length ? events[i + 1] : null, - previousEvent: i > 0 ? events[i - 1] : null, - avatarPresenceBackgroundColor: - hasWallpaper ? Colors.transparent : null, + return Message( + event, + animateIn: animateIn, + resetAnimateIn: () { + controller.animateInEventIndex = null; + }, + onSwipe: () => controller.replyAction(replyTo: event), + onInfoTab: controller.showEventInfo, + onAvatarTab: (Event event) => showAdaptiveBottomSheet( + context: context, + builder: (c) => UserBottomSheet( + user: event.senderFromMemoryOrFallback, + outerContext: context, + onMention: () => controller.sendController.text += + '${event.senderFromMemoryOrFallback.mention} ', ), - ); - }, - childCount: events.length + 2, - findChildIndexCallback: (key) => - controller.findChildIndexCallback(key, thisEventsKeyMap), - ), + ), + highlightMarker: controller.scrollToEventIdMarker == event.eventId, + onSelect: controller.onSelectMessage, + scrollToEventId: (String eventId) => + controller.scrollToEventId(eventId), + longPressSelect: controller.selectedEvents.isNotEmpty, + selected: controller.selectedEvents + .any((e) => e.eventId == event.eventId), + timeline: controller.timeline!, + displayReadMarker: controller.readMarkerEventId == event.eventId && + controller.timeline?.allowNewEvent == false, + nextEvent: i + 1 < events.length ? events[i + 1] : null, + previousEvent: i > 0 ? events[i - 1] : null, + avatarPresenceBackgroundColor: + hasWallpaper ? Colors.transparent : null, + ); + }, ), ); } diff --git a/pubspec.lock b/pubspec.lock index 7e3909e54..97a1137bd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1694,14 +1694,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" - scroll_to_index: + scrollable_positioned_list: dependency: "direct main" description: - name: scroll_to_index - sha256: b707546e7500d9f070d63e5acf74fd437ec7eeeb68d3412ef7b0afada0b4f176 + name: scrollable_positioned_list + sha256: "1b54d5f1329a1e263269abc9e2543d90806131aa14fe7c6062a8054d57249287" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "0.3.8" sdp_transform: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a013aeea3..38c2734d3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -77,7 +77,7 @@ dependencies: qr_code_scanner: ^1.0.1 receive_sharing_intent: 1.4.5 # Update needs more work record: ^5.1.2 - scroll_to_index: ^3.0.1 + scrollable_positioned_list: ^0.3.8 share_plus: ^9.0.0 shared_preferences: ^2.2.0 # Pinned because https://github.com/flutter/flutter/issues/118401 slugify: ^2.0.0 @@ -160,4 +160,4 @@ dependency_overrides: git: url: https://github.com/TheOneWithTheBraid/keyboard_shortcuts.git ref: null-safety - win32: 5.5.0 \ No newline at end of file + win32: 5.5.0