pr adjustments

This commit is contained in:
vineyardbovines
2026-03-04 08:36:30 -05:00
parent 7ef7656dcb
commit c6f2d8e235
+31 -34
View File
@@ -156,17 +156,23 @@ export function PagerWithHeader({
[currentPage, headerOnlyHeight, lastForcedScrollY, scrollRefs, scrollY], [currentPage, headerOnlyHeight, lastForcedScrollY, scrollRefs, scrollY],
) )
// HACK: onScroll reports a bogus value of exactly -headerHeight on initial // HACK: onScroll reports bogus values of exactly -headerHeight on initial
// load. We filter it out, but only until the first real scroll event has // load and again after header height changes. We filter them out until
// been accepted. After that we stop filtering so that legitimate // the first real scroll event arrives for each header height. This lets
// scroll-to-top events (which also target -headerHeight) are not rejected. // legitimate scroll-to-top events through while still rejecting the
// profiles with very large headers do scroll to -headerHeight normally, // spurious ones that would cause visual jumps. - sfp
// so we need to adjust the heuristic. - sfp
const hasReceivedScroll = useSharedValue(false) const hasReceivedScroll = useSharedValue(false)
const lastKnownHeaderHeight = useSharedValue(0)
const onScrollWorklet = useCallback( const onScrollWorklet = useCallback(
(e: NativeScrollEvent) => { (e: NativeScrollEvent) => {
'worklet' 'worklet'
const nextScrollY = e.contentOffset.y 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()) { if (!hasReceivedScroll.get()) {
const isPossiblyInvalid = const isPossiblyInvalid =
headerHeight > 0 && Math.round(nextScrollY * 2) / 2 === -headerHeight headerHeight > 0 && Math.round(nextScrollY * 2) / 2 === -headerHeight
@@ -177,7 +183,7 @@ export function PagerWithHeader({
} }
scrollY.set(nextScrollY) scrollY.set(nextScrollY)
}, },
[scrollY, headerHeight, hasReceivedScroll], [scrollY, headerHeight, hasReceivedScroll, lastKnownHeaderHeight],
) )
const onPageSelectedInner = useCallback( const onPageSelectedInner = useCallback(
@@ -277,35 +283,30 @@ let PagerTabBar = ({
], ],
} }
}) })
const headerRef = useRef<View>(null) const pendingHeaderHeightForWhenSentinelReady = useRef<number | undefined>(
const fallbackHeaderOnlyHeight = useRef(0) undefined,
)
const sentinelHasRenderedRef = useRef(false)
return ( return (
<Animated.View <Animated.View
pointerEvents={IS_IOS ? 'auto' : 'box-none'} pointerEvents={IS_IOS ? 'auto' : 'box-none'}
style={[styles.tabBarMobile, headerTransform, t.atoms.bg]}> style={[styles.tabBarMobile, headerTransform, t.atoms.bg]}>
<View <View
ref={headerRef}
pointerEvents={IS_IOS ? 'auto' : 'box-none'} pointerEvents={IS_IOS ? 'auto' : 'box-none'}
collapsable={false} collapsable={false}
onLayout={(e: LayoutChangeEvent) => { onLayout={(e: LayoutChangeEvent) => {
// 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 const height = e.nativeEvent.layout.height
// Fallback measurement using onLayout directly on the header wrapper. // note: sentinel only renders after `isHeaderReady` has turned `true`
// This is more reliable than .measure() on Android after certain if (sentinelHasRenderedRef.current) {
// 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) onHeaderOnlyLayout(height)
} } else {
pendingHeaderHeightForWhenSentinelReady.current = height
} }
}}> }}>
{renderHeader?.({setMinimumHeight: setMinimumHeaderHeight})} {renderHeader?.({setMinimumHeight: setMinimumHeaderHeight})}
@@ -320,17 +321,13 @@ let PagerTabBar = ({
// even if `isHeaderReady` might have turned `true`, the associated // even if `isHeaderReady` might have turned `true`, the associated
// layout might not have been performed yet on the native side. // layout might not have been performed yet on the native side.
onLayout={() => { onLayout={() => {
headerRef.current?.measure( if (!sentinelHasRenderedRef.current) {
(_x: number, _y: number, _width: number, height: number) => { sentinelHasRenderedRef.current = true
// sometimes height is `undefined` on Android, see above const height = pendingHeaderHeightForWhenSentinelReady.current
if (height !== undefined) { if (height !== undefined) {
onHeaderOnlyLayout(height) onHeaderOnlyLayout(height)
} else {
// if measure fails, use the value we got from `onLayout`
onHeaderOnlyLayout(fallbackHeaderOnlyHeight.current)
} }
}, }
)
}} }}
/> />
) )