add jump to bottom button
This commit is contained in:
parent
1d77f71950
commit
0ee0256383
2 changed files with 72 additions and 26 deletions
|
|
@ -17,14 +17,15 @@ final class ChatScroll({
|
|||
required final ListController historyListController,
|
||||
required final ListController liveListController,
|
||||
required final ScrollController scrollController,
|
||||
required final bool atBottom,
|
||||
required final Future<void> Function(String id) jumpToId,
|
||||
required final Future<void> Function() jumpToBottom,
|
||||
}) {
|
||||
factory use({
|
||||
required AsyncValue<RoomChat?> controllerData,
|
||||
required Future<void> Function(Direction direction) paginate,
|
||||
required Future<void> Function() markRead,
|
||||
required String? contextualEvent,
|
||||
required void Function(String id) setContextualEvent,
|
||||
required ValueNotifier<String?> contextualEvent,
|
||||
}) {
|
||||
final anchorId = useState<String?>(null);
|
||||
|
||||
|
|
@ -35,6 +36,8 @@ final class ChatScroll({
|
|||
final scrollController = useScrollController();
|
||||
final centerKey = useMemoized(GlobalKey.new);
|
||||
|
||||
final atBottom = useState(true);
|
||||
|
||||
final pendingAnchorTarget = useState<String?>(null);
|
||||
final anchorMountedCompleter = useRef<Completer<BuildContext>?>(null);
|
||||
|
||||
|
|
@ -43,17 +46,17 @@ final class ChatScroll({
|
|||
if (controllerData case AsyncData(:final value?)
|
||||
when value.timeline.isNotEmpty) {
|
||||
final hasContextualEvent = value.timeline.any(
|
||||
(event) => event.eventId == contextualEvent,
|
||||
(event) => event.eventId == contextualEvent.value,
|
||||
);
|
||||
|
||||
anchorId.value = hasContextualEvent
|
||||
? contextualEvent
|
||||
? contextualEvent.value
|
||||
: value.timeline.last.eventId;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [controllerData, contextualEvent]);
|
||||
}, [controllerData, contextualEvent.value]);
|
||||
|
||||
useEffect(() {
|
||||
final target = pendingAnchorTarget.value;
|
||||
|
|
@ -115,31 +118,43 @@ final class ChatScroll({
|
|||
);
|
||||
}, [controllerData, anchorId.value]);
|
||||
|
||||
useEffect(() {
|
||||
const loadThreshold = 500.0;
|
||||
const readThreshold = 50.0;
|
||||
useEffect(
|
||||
() {
|
||||
const loadThreshold = 500.0;
|
||||
const readThreshold = 50.0;
|
||||
|
||||
Future<void> checkPosition() async {
|
||||
if (!scrollController.hasClients) return;
|
||||
Future<void> checkPosition() async {
|
||||
if (!scrollController.hasClients) return;
|
||||
|
||||
final position = scrollController.position;
|
||||
final position = scrollController.position;
|
||||
|
||||
if (position.extentAfter <= loadThreshold) {
|
||||
await paginate(.backward);
|
||||
} else if (contextualEvent != null &&
|
||||
position.extentBefore <= loadThreshold) {
|
||||
await paginate(.forward);
|
||||
} else if (position.extentBefore <= readThreshold) {
|
||||
await markRead();
|
||||
final isAtBottom = position.extentBefore <= readThreshold;
|
||||
if (isAtBottom != atBottom.value) atBottom.value = isAtBottom;
|
||||
|
||||
if (position.extentAfter <= loadThreshold) {
|
||||
await paginate(.backward);
|
||||
} else if (contextualEvent.value != null &&
|
||||
position.extentBefore <= loadThreshold) {
|
||||
await paginate(.forward);
|
||||
} else if (position.extentBefore <= readThreshold) {
|
||||
await markRead();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scrollController.addListener(checkPosition);
|
||||
scrollController.addListener(checkPosition);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => checkPosition());
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => checkPosition());
|
||||
|
||||
return () => scrollController.removeListener(checkPosition);
|
||||
}, [scrollController, controllerData, paginate, markRead, contextualEvent]);
|
||||
return () => scrollController.removeListener(checkPosition);
|
||||
},
|
||||
[
|
||||
scrollController,
|
||||
controllerData,
|
||||
paginate,
|
||||
markRead,
|
||||
contextualEvent.value,
|
||||
],
|
||||
);
|
||||
|
||||
double? resolveOffset(String itemId) {
|
||||
final historyIndex = split.history.indexWhere(
|
||||
|
|
@ -177,7 +192,7 @@ final class ChatScroll({
|
|||
final completer = Completer<BuildContext>();
|
||||
anchorMountedCompleter.value = completer;
|
||||
pendingAnchorTarget.value = itemId;
|
||||
setContextualEvent(itemId);
|
||||
contextualEvent.value = itemId;
|
||||
|
||||
final context = await completer.future;
|
||||
|
||||
|
|
@ -191,6 +206,21 @@ final class ChatScroll({
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> jumpToBottom() async {
|
||||
if (contextualEvent.value != null) {
|
||||
anchorId.value = null;
|
||||
contextualEvent.value = null;
|
||||
}
|
||||
|
||||
if (!scrollController.hasClients) return;
|
||||
|
||||
await scrollController.animateTo(
|
||||
scrollController.position.minScrollExtent,
|
||||
duration: const .new(milliseconds: 700),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
|
||||
return .new(
|
||||
historyItems: split.history,
|
||||
liveItems: split.live,
|
||||
|
|
@ -199,7 +229,9 @@ final class ChatScroll({
|
|||
historyListController: historyListController,
|
||||
liveListController: liveListController,
|
||||
scrollController: scrollController,
|
||||
atBottom: atBottom.value,
|
||||
jumpToId: jumpToId,
|
||||
jumpToBottom: jumpToBottom,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,8 +74,7 @@ final class const RoomChat({
|
|||
final scroll = ChatScroll.use(
|
||||
controllerData: controllerData,
|
||||
paginate: notifier.paginate,
|
||||
contextualEvent: contextualEvent.value,
|
||||
setContextualEvent: (id) => contextualEvent.value = id,
|
||||
contextualEvent: contextualEvent,
|
||||
markRead: () async {
|
||||
final room = ref.read(
|
||||
RoomsController.provider.select((rooms) => rooms[roomId]),
|
||||
|
|
@ -159,6 +158,21 @@ final class const RoomChat({
|
|||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 16,
|
||||
bottom: composerSize.value,
|
||||
child: IgnorePointer(
|
||||
ignoring: scroll.atBottom,
|
||||
child: AnimatedOpacity(
|
||||
opacity: scroll.atBottom ? 0 : 1,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: FloatingActionButton.small(
|
||||
onPressed: scroll.jumpToBottom,
|
||||
child: const Icon(Icons.keyboard_arrow_down),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
|
|
|
|||
Loading…
Reference in a new issue