From c6f2d8e235dc203b6bc23b7f4de698bf56f52d0c Mon Sep 17 00:00:00 2001 From: vineyardbovines Date: Wed, 4 Mar 2026 08:36:30 -0500 Subject: [PATCH] pr adjustments --- src/view/com/pager/PagerWithHeader.tsx | 73 ++++++++++++-------------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/src/view/com/pager/PagerWithHeader.tsx b/src/view/com/pager/PagerWithHeader.tsx index abc6d024e5..fb4022d44d 100644 --- a/src/view/com/pager/PagerWithHeader.tsx +++ b/src/view/com/pager/PagerWithHeader.tsx @@ -156,17 +156,23 @@ export function PagerWithHeader({ [currentPage, headerOnlyHeight, lastForcedScrollY, scrollRefs, scrollY], ) - // HACK: onScroll reports a bogus value of exactly -headerHeight on initial - // load. We filter it out, but only until the first real scroll event has - // been accepted. After that we stop filtering so that legitimate - // scroll-to-top events (which also target -headerHeight) are not rejected. - // profiles with very large headers do scroll to -headerHeight normally, - // so we need to adjust the heuristic. - sfp + // HACK: onScroll reports bogus values of exactly -headerHeight on initial + // load and again after header height changes. We filter them out until + // the first real scroll event arrives for each header height. This lets + // legitimate scroll-to-top events through while still rejecting the + // spurious ones that would cause visual jumps. - sfp const hasReceivedScroll = useSharedValue(false) + const lastKnownHeaderHeight = useSharedValue(0) const onScrollWorklet = useCallback( (e: NativeScrollEvent) => { 'worklet' const nextScrollY = e.contentOffset.y + // Reset the filter whenever the header height changes, because + // a new batch of bogus -headerHeight events will appear. + if (headerHeight !== lastKnownHeaderHeight.get()) { + lastKnownHeaderHeight.set(headerHeight) + hasReceivedScroll.set(false) + } if (!hasReceivedScroll.get()) { const isPossiblyInvalid = headerHeight > 0 && Math.round(nextScrollY * 2) / 2 === -headerHeight @@ -177,7 +183,7 @@ export function PagerWithHeader({ } scrollY.set(nextScrollY) }, - [scrollY, headerHeight, hasReceivedScroll], + [scrollY, headerHeight, hasReceivedScroll, lastKnownHeaderHeight], ) const onPageSelectedInner = useCallback( @@ -277,35 +283,30 @@ let PagerTabBar = ({ ], } }) - const headerRef = useRef(null) - const fallbackHeaderOnlyHeight = useRef(0) + const pendingHeaderHeightForWhenSentinelReady = useRef( + undefined, + ) + const sentinelHasRenderedRef = useRef(false) return ( { + // we want to measure this view's height to get the header height. + // however, we risk doing it too early if the header hasn't rendered yet. + // therefore, we use a sentinel view and wait for *that* to layout before + // we set the header height, using the last measured height from this + // onLayout. after the sentinel has rendered for the first time, we can + // just straightforwardly set the header height here directly -sfn const height = e.nativeEvent.layout.height - // Fallback measurement using onLayout directly on the header wrapper. - // This is more reliable than .measure() on Android after certain - // navigation transitions (e.g. returning from the logged-out view) - // where .measure() can fail to return a height. in general though, - // we should prefer using .measure() when possible as this can - // fire too early and cause layout thrashing. - // ref: https://github.com/bluesky-social/social-app/pull/9964 -sfp - if (isHeaderReady) { - fallbackHeaderOnlyHeight.current = height - // Re-measure when the header content changes size (e.g. - // SuggestedFollows accordion expanding/collapsing). The sentinel - // view below only fires onLayout once on mount, so without this - // the headerOnlyHeight goes stale. - sfp - const rounded = Math.round(height * 2) / 2 - if (rounded > 0 && rounded !== headerOnlyHeight) { - onHeaderOnlyLayout(height) - } + // note: sentinel only renders after `isHeaderReady` has turned `true` + if (sentinelHasRenderedRef.current) { + onHeaderOnlyLayout(height) + } else { + pendingHeaderHeightForWhenSentinelReady.current = height } }}> {renderHeader?.({setMinimumHeight: setMinimumHeaderHeight})} @@ -320,17 +321,13 @@ let PagerTabBar = ({ // even if `isHeaderReady` might have turned `true`, the associated // layout might not have been performed yet on the native side. onLayout={() => { - headerRef.current?.measure( - (_x: number, _y: number, _width: number, height: number) => { - // sometimes height is `undefined` on Android, see above - if (height !== undefined) { - onHeaderOnlyLayout(height) - } else { - // if measure fails, use the value we got from `onLayout` - onHeaderOnlyLayout(fallbackHeaderOnlyHeight.current) - } - }, - ) + if (!sentinelHasRenderedRef.current) { + sentinelHasRenderedRef.current = true + const height = pendingHeaderHeightForWhenSentinelReady.current + if (height !== undefined) { + onHeaderOnlyLayout(height) + } + } }} /> )