From 72321cc4138cf30a972231ca5e9fd1cc4f6516db Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Fri, 25 Sep 2026 21:24:10 -0400 Subject: [PATCH] clean up approach to avoid polling --- lib/helpers/hooks/chat_scroll.dart | 51 ++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/lib/helpers/hooks/chat_scroll.dart b/lib/helpers/hooks/chat_scroll.dart index c2d047d..f0d4f1d 100644 --- a/lib/helpers/hooks/chat_scroll.dart +++ b/lib/helpers/hooks/chat_scroll.dart @@ -28,8 +28,6 @@ final class ChatScroll({ final anchorId = useState(null); final anchorItemKey = useMemoized(GlobalKey.new, [anchorId.value]); - final anchorItemKeyRef = useRef(anchorItemKey); - anchorItemKeyRef.value = anchorItemKey; final historyListController = useMemoized(ListController.new, [ anchorId.value, @@ -41,6 +39,7 @@ final class ChatScroll({ final centerKey = useMemoized(GlobalKey.new); final pendingAnchorTarget = useState(null); + final anchorMountedCompleter = useRef?>(null); useEffect(() { if (anchorId.value == null) { @@ -70,13 +69,35 @@ final class ChatScroll({ false; if (found || controllerData is AsyncError) { - if (found) anchorId.value = target; + if (found) { + anchorId.value = target; + } else { + anchorMountedCompleter.value?.completeError( + StateError("Failed to load context for $target"), + ); + anchorMountedCompleter.value = null; + } pendingAnchorTarget.value = null; } return null; }, [controllerData, pendingAnchorTarget.value]); + useEffect(() { + final completer = anchorMountedCompleter.value; + if (completer == null) return null; + + WidgetsBinding.instance.addPostFrameCallback((_) { + final context = anchorItemKey.currentContext; + if (context != null && context.mounted) { + anchorMountedCompleter.value?.complete(context); + anchorMountedCompleter.value = null; + } + }); + + return null; + }, [anchorId.value]); + final ({IList history, IList live}) split = useMemoized(() { final items = controllerData.value?.timeline; final anchor = anchorId.value; @@ -157,24 +178,20 @@ final class ChatScroll({ return; } + final completer = Completer(); + anchorMountedCompleter.value = completer; pendingAnchorTarget.value = itemId; setContextualEvent(itemId); - for (var i = 0; i < 60; i++) { - if (anchorId.value == itemId) { - final context = anchorItemKeyRef.value.currentContext; - if (context != null && context.mounted) { - await Scrollable.ensureVisible( - context, - alignment: 0.5, - duration: const Duration(milliseconds: 700), - curve: Curves.easeInOut, - ); - return; - } - } + final context = await completer.future; - await Future.delayed(const Duration(milliseconds: 16)); + if (context.mounted) { + await Scrollable.ensureVisible( + context, + alignment: 0.5, + duration: const Duration(milliseconds: 700), + curve: Curves.easeInOut, + ); } }