Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
Krille
57db6c88cd
refactor: Migrate to scrollabel list 2024-07-28 09:49:05 +02:00
4 changed files with 126 additions and 126 deletions

View file

@ -16,7 +16,7 @@ import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:record/record.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:shared_preferences/shared_preferences.dart';
import 'package:universal_html/html.dart' as html; 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/pages/chat_details/chat_details.dart';
import 'package:fluffychat/utils/error_reporter.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/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/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/app_lock.dart'; import 'package:fluffychat/widgets/app_lock.dart';
@ -104,7 +105,13 @@ class ChatController extends State<ChatPageWithRoom>
String get roomId => widget.room.id; 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(); FocusNode inputFocus = FocusNode();
StreamSubscription<html.Event>? onFocusSub; StreamSubscription<html.Event>? onFocusSub;
@ -230,21 +237,20 @@ class ChatController extends State<ChatPageWithRoom>
setReadMarker(eventId: mostRecentEventId); setReadMarker(eventId: mostRecentEventId);
} }
void _updateScrollController() { void _updateScrollController(double offset) {
if (!mounted) { if (!mounted) {
return; return;
} }
if (!scrollController.hasClients) return;
if (timeline?.allowNewEvent == false || if (timeline?.allowNewEvent == false ||
scrollController.position.pixels > 0 && _scrolledUp == false) { offset > 2 && _scrolledUp == false) {
setState(() => _scrolledUp = true); setState(() => _scrolledUp = true);
} else if (scrollController.position.pixels <= 0 && _scrolledUp == true) { } else if (offset <= 2 && _scrolledUp == true) {
setState(() => _scrolledUp = false); setState(() => _scrolledUp = false);
setReadMarker(); setReadMarker();
} }
if (scrollController.position.pixels == 0 || if (offset == 0 || offset == 64) {
scrollController.position.pixels == 64) {
requestFuture(); requestFuture();
} }
} }
@ -259,7 +265,7 @@ class ChatController extends State<ChatPageWithRoom>
@override @override
void initState() { void initState() {
scrollController.addListener(_updateScrollController); scrollOffsetListener.changes.listen(_updateScrollController);
inputFocus.addListener(_inputFocusListener); inputFocus.addListener(_inputFocusListener);
_loadDraft(); _loadDraft();
@ -292,7 +298,7 @@ class ChatController extends State<ChatPageWithRoom>
if (timeline?.events.any((event) => event.eventId == fullyRead) ?? if (timeline?.events.any((event) => event.eventId == fullyRead) ??
false) { false) {
Logs().v('Scroll up to visible event', fullyRead); Logs().v('Scroll up to visible event', fullyRead);
setReadMarker(); scrollToEventId(fullyRead);
return; return;
} }
if (!mounted) return; if (!mounted) return;
@ -901,7 +907,9 @@ class ChatController extends State<ChatPageWithRoom>
} }
void scrollToEventId(String eventId) async { 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) { if (eventIndex == -1) {
setState(() { setState(() {
timeline = null; timeline = null;
@ -920,11 +928,11 @@ class ChatController extends State<ChatPageWithRoom>
setState(() { setState(() {
scrollToEventIdMarker = eventId; scrollToEventIdMarker = eventId;
}); });
await scrollController.scrollToIndex( await itemScrollController.scrollTo(
eventIndex, index: eventIndex + 1,
preferPosition: AutoScrollPosition.middle, duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
); );
_updateScrollController();
} }
void scrollDown() async { void scrollDown() async {
@ -939,7 +947,11 @@ class ChatController extends State<ChatPageWithRoom>
}); });
await loadTimelineFuture; await loadTimelineFuture;
} }
scrollController.jumpTo(0); itemScrollController.scrollTo(
index: 0,
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
);
} }
void onEmojiSelected(_, Emoji? emoji) { void onEmojiSelected(_, Emoji? emoji) {

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:matrix/matrix.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/config/themes.dart';
import 'package:fluffychat/pages/chat/chat.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/account_config.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart';
import 'package:fluffychat/utils/platform_infos.dart';
class ChatEventList extends StatelessWidget { class ChatEventList extends StatelessWidget {
final ChatController controller; final ChatController controller;
@ -41,119 +40,108 @@ class ChatEventList extends StatelessWidget {
controller.room.client.applicationAccountConfig.wallpaperUrl != null; controller.room.client.applicationAccountConfig.wallpaperUrl != null;
return SelectionArea( return SelectionArea(
child: ListView.custom( child: ScrollablePositionedList.builder(
padding: EdgeInsets.only( padding: EdgeInsets.only(
top: 16, top: 16,
bottom: 8, bottom: 8,
left: horizontalPadding, left: horizontalPadding,
right: horizontalPadding, right: horizontalPadding,
), ),
itemCount: events.length + 2,
reverse: true, reverse: true,
controller: controller.scrollController, itemScrollController: controller.itemScrollController,
keyboardDismissBehavior: PlatformInfos.isIOS scrollOffsetController: controller.scrollOffsetController,
? ScrollViewKeyboardDismissBehavior.onDrag itemPositionsListener: controller.itemPositionsListener,
: ScrollViewKeyboardDismissBehavior.manual, scrollOffsetListener: controller.scrollOffsetListener,
childrenDelegate: SliverChildBuilderDelegate( itemBuilder: (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) {
if (controller.timeline!.isRequestingFuture) { return const Center(
return const Center( child: CircularProgressIndicator.adaptive(strokeWidth: 2),
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),
],
); );
} }
if (controller.timeline!.canRequestFuture) {
// Request history button or progress indicator: return Center(
if (i == events.length + 1) { child: IconButton(
if (controller.timeline!.isRequestingHistory) { onPressed: controller.requestFuture,
return const Center( icon: const Icon(Icons.refresh_outlined),
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();
}
i--;
// 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, return Column(
scrollToEventId: (String eventId) => mainAxisSize: MainAxisSize.min,
controller.scrollToEventId(eventId), children: [
longPressSelect: controller.selectedEvents.isNotEmpty, SeenByRow(controller),
selected: controller.selectedEvents TypingIndicators(controller),
.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,
),
); );
}, }
childCount: events.length + 2,
findChildIndexCallback: (key) => // Request history button or progress indicator:
controller.findChildIndexCallback(key, thisEventsKeyMap), 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();
}
i--;
// The message at this index:
final event = events[i];
final animateIn = animateInEventIndex != null &&
controller.timeline!.events.length > animateInEventIndex &&
event == controller.timeline!.events[animateInEventIndex];
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} ',
),
),
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,
);
},
), ),
); );
} }

View file

@ -1694,14 +1694,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.2" version: "1.1.2"
scroll_to_index: scrollable_positioned_list:
dependency: "direct main" dependency: "direct main"
description: description:
name: scroll_to_index name: scrollable_positioned_list
sha256: b707546e7500d9f070d63e5acf74fd437ec7eeeb68d3412ef7b0afada0b4f176 sha256: "1b54d5f1329a1e263269abc9e2543d90806131aa14fe7c6062a8054d57249287"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.1" version: "0.3.8"
sdp_transform: sdp_transform:
dependency: transitive dependency: transitive
description: description:

View file

@ -77,7 +77,7 @@ dependencies:
qr_code_scanner: ^1.0.1 qr_code_scanner: ^1.0.1
receive_sharing_intent: 1.4.5 # Update needs more work receive_sharing_intent: 1.4.5 # Update needs more work
record: ^5.1.2 record: ^5.1.2
scroll_to_index: ^3.0.1 scrollable_positioned_list: ^0.3.8
share_plus: ^9.0.0 share_plus: ^9.0.0
shared_preferences: ^2.2.0 # Pinned because https://github.com/flutter/flutter/issues/118401 shared_preferences: ^2.2.0 # Pinned because https://github.com/flutter/flutter/issues/118401
slugify: ^2.0.0 slugify: ^2.0.0
@ -160,4 +160,4 @@ dependency_overrides:
git: git:
url: https://github.com/TheOneWithTheBraid/keyboard_shortcuts.git url: https://github.com/TheOneWithTheBraid/keyboard_shortcuts.git
ref: null-safety ref: null-safety
win32: 5.5.0 win32: 5.5.0