Bluesky app fork with some witchin' additions 💫

[APP-1917] fix header measurement for android content disappearing on switch (#9964)

Co-authored-by: Samuel Newman <mozzius@protonmail.com>

authored by

Spence Pope
Samuel Newman
and committed by
GitHub
a95b877e f614bf65

+27 -9
+27 -9
src/view/com/pager/PagerWithHeader.tsx
··· 16 16 useSharedValue, 17 17 } from 'react-native-reanimated' 18 18 19 - import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 20 19 import {ScrollProvider} from '#/lib/ScrollContext' 21 20 import { 22 21 Pager, ··· 72 71 const headerHeight = headerOnlyHeight + tabBarHeight 73 72 74 73 // capture the header bar sizing 75 - const onTabBarLayout = useNonReactiveCallback((evt: LayoutChangeEvent) => { 74 + const onTabBarLayout = useCallback((evt: LayoutChangeEvent) => { 76 75 const height = evt.nativeEvent.layout.height 77 76 if (height > 0) { 78 77 // The rounding is necessary to prevent jumps on iOS 79 78 setTabBarHeight(Math.round(height * 2) / 2) 80 79 } 81 - }) 82 - const onHeaderOnlyLayout = useNonReactiveCallback((height: number) => { 80 + }, []) 81 + const onHeaderOnlyLayout = useCallback((height: number) => { 83 82 if (height > 0) { 84 83 // The rounding is necessary to prevent jumps on iOS 85 84 setHeaderOnlyHeight(Math.round(height * 2) / 2) 86 85 } 87 - }) 86 + }, []) 88 87 89 88 const renderTabBar = useCallback( 90 89 (props: RenderTabBarFnProps) => { ··· 270 269 ], 271 270 } 272 271 }) 273 - const headerRef = useRef(null) 272 + const headerRef = useRef<View>(null) 273 + const fallbackHeaderOnlyHeight = useRef(0) 274 274 return ( 275 275 <Animated.View 276 276 pointerEvents={IS_IOS ? 'auto' : 'box-none'} ··· 278 278 <View 279 279 ref={headerRef} 280 280 pointerEvents={IS_IOS ? 'auto' : 'box-none'} 281 - collapsable={false}> 281 + collapsable={false} 282 + onLayout={(e: LayoutChangeEvent) => { 283 + // Fallback measurement using onLayout directly on the header wrapper. 284 + // This is more reliable than .measure() on Android after certain 285 + // navigation transitions (e.g. returning from the logged-out view) 286 + // where .measure() can fail to return a height. in general though, 287 + // we should prefer using .measure() when possible as this can 288 + // fire too early and cause layout thrashing. 289 + // ref: https://github.com/bluesky-social/social-app/pull/9964 -sfp 290 + if (isHeaderReady) { 291 + fallbackHeaderOnlyHeight.current = e.nativeEvent.layout.height 292 + } 293 + }}> 282 294 {renderHeader?.({setMinimumHeight: setMinimumHeaderHeight})} 283 295 { 284 296 // It wouldn't be enough to place `onLayout` on the parent node because ··· 286 298 // Instead, we'll render a brand node conditionally and get fresh layout. 287 299 isHeaderReady && ( 288 300 <View 301 + collapsable={false} 289 302 // It wouldn't be enough to do this in a `ref` of an effect because, 290 303 // even if `isHeaderReady` might have turned `true`, the associated 291 304 // layout might not have been performed yet on the native side. 292 305 onLayout={() => { 293 - // @ts-ignore 294 306 headerRef.current?.measure( 295 307 (_x: number, _y: number, _width: number, height: number) => { 296 - onHeaderOnlyLayout(height) 308 + // sometimes height is `undefined` on Android, see above 309 + if (height !== undefined) { 310 + onHeaderOnlyLayout(height) 311 + } else { 312 + // if measure fails, use the value we got from `onLayout` 313 + onHeaderOnlyLayout(fallbackHeaderOnlyHeight.current) 314 + } 297 315 }, 298 316 ) 299 317 }}