clean up approach to avoid polling

This commit is contained in:
Henry Hiles 2026-09-25 21:24:10 -04:00
commit 72321cc413
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs

View file

@ -28,8 +28,6 @@ final class ChatScroll({
final anchorId = useState<String?>(null); final anchorId = useState<String?>(null);
final anchorItemKey = useMemoized(GlobalKey.new, [anchorId.value]); final anchorItemKey = useMemoized(GlobalKey.new, [anchorId.value]);
final anchorItemKeyRef = useRef(anchorItemKey);
anchorItemKeyRef.value = anchorItemKey;
final historyListController = useMemoized(ListController.new, [ final historyListController = useMemoized(ListController.new, [
anchorId.value, anchorId.value,
@ -41,6 +39,7 @@ final class ChatScroll({
final centerKey = useMemoized(GlobalKey.new); final centerKey = useMemoized(GlobalKey.new);
final pendingAnchorTarget = useState<String?>(null); final pendingAnchorTarget = useState<String?>(null);
final anchorMountedCompleter = useRef<Completer<BuildContext>?>(null);
useEffect(() { useEffect(() {
if (anchorId.value == null) { if (anchorId.value == null) {
@ -70,13 +69,35 @@ final class ChatScroll({
false; false;
if (found || controllerData is AsyncError) { 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; pendingAnchorTarget.value = null;
} }
return null; return null;
}, [controllerData, pendingAnchorTarget.value]); }, [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<Event> history, IList<Event> live}) split = useMemoized(() { final ({IList<Event> history, IList<Event> live}) split = useMemoized(() {
final items = controllerData.value?.timeline; final items = controllerData.value?.timeline;
final anchor = anchorId.value; final anchor = anchorId.value;
@ -157,24 +178,20 @@ final class ChatScroll({
return; return;
} }
final completer = Completer<BuildContext>();
anchorMountedCompleter.value = completer;
pendingAnchorTarget.value = itemId; pendingAnchorTarget.value = itemId;
setContextualEvent(itemId); setContextualEvent(itemId);
for (var i = 0; i < 60; i++) { final context = await completer.future;
if (anchorId.value == itemId) {
final context = anchorItemKeyRef.value.currentContext; if (context.mounted) {
if (context != null && context.mounted) {
await Scrollable.ensureVisible( await Scrollable.ensureVisible(
context, context,
alignment: 0.5, alignment: 0.5,
duration: const Duration(milliseconds: 700), duration: const Duration(milliseconds: 700),
curve: Curves.easeInOut, curve: Curves.easeInOut,
); );
return;
}
}
await Future.delayed(const Duration(milliseconds: 16));
} }
} }