···16 useSharedValue,
17} from 'react-native-reanimated'
1819-import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
20import {ScrollProvider} from '#/lib/ScrollContext'
21import {
22 Pager,
···72 const headerHeight = headerOnlyHeight + tabBarHeight
7374 // capture the header bar sizing
75- const onTabBarLayout = useNonReactiveCallback((evt: LayoutChangeEvent) => {
76 const height = evt.nativeEvent.layout.height
77 if (height > 0) {
78 // The rounding is necessary to prevent jumps on iOS
79 setTabBarHeight(Math.round(height * 2) / 2)
80 }
81- })
82- const onHeaderOnlyLayout = useNonReactiveCallback((height: number) => {
83 if (height > 0) {
84 // The rounding is necessary to prevent jumps on iOS
85 setHeaderOnlyHeight(Math.round(height * 2) / 2)
86 }
87- })
8889 const renderTabBar = useCallback(
90 (props: RenderTabBarFnProps) => {
···270 ],
271 }
272 })
273- const headerRef = useRef(null)
0274 return (
275 <Animated.View
276 pointerEvents={IS_IOS ? 'auto' : 'box-none'}
···278 <View
279 ref={headerRef}
280 pointerEvents={IS_IOS ? 'auto' : 'box-none'}
281- collapsable={false}>
000000000000282 {renderHeader?.({setMinimumHeight: setMinimumHeaderHeight})}
283 {
284 // It wouldn't be enough to place `onLayout` on the parent node because
···286 // Instead, we'll render a brand node conditionally and get fresh layout.
287 isHeaderReady && (
288 <View
0289 // It wouldn't be enough to do this in a `ref` of an effect because,
290 // even if `isHeaderReady` might have turned `true`, the associated
291 // layout might not have been performed yet on the native side.
292 onLayout={() => {
293- // @ts-ignore
294 headerRef.current?.measure(
295 (_x: number, _y: number, _width: number, height: number) => {
296- onHeaderOnlyLayout(height)
000000297 },
298 )
299 }}
···16 useSharedValue,
17} from 'react-native-reanimated'
18019import {ScrollProvider} from '#/lib/ScrollContext'
20import {
21 Pager,
···71 const headerHeight = headerOnlyHeight + tabBarHeight
7273 // capture the header bar sizing
74+ const onTabBarLayout = useCallback((evt: LayoutChangeEvent) => {
75 const height = evt.nativeEvent.layout.height
76 if (height > 0) {
77 // The rounding is necessary to prevent jumps on iOS
78 setTabBarHeight(Math.round(height * 2) / 2)
79 }
80+ }, [])
81+ const onHeaderOnlyLayout = useCallback((height: number) => {
82 if (height > 0) {
83 // The rounding is necessary to prevent jumps on iOS
84 setHeaderOnlyHeight(Math.round(height * 2) / 2)
85 }
86+ }, [])
8788 const renderTabBar = useCallback(
89 (props: RenderTabBarFnProps) => {
···269 ],
270 }
271 })
272+ const headerRef = useRef<View>(null)
273+ const fallbackHeaderOnlyHeight = useRef(0)
274 return (
275 <Animated.View
276 pointerEvents={IS_IOS ? 'auto' : 'box-none'}
···278 <View
279 ref={headerRef}
280 pointerEvents={IS_IOS ? 'auto' : 'box-none'}
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+ }}>
294 {renderHeader?.({setMinimumHeight: setMinimumHeaderHeight})}
295 {
296 // It wouldn't be enough to place `onLayout` on the parent node because
···298 // Instead, we'll render a brand node conditionally and get fresh layout.
299 isHeaderReady && (
300 <View
301+ collapsable={false}
302 // It wouldn't be enough to do this in a `ref` of an effect because,
303 // even if `isHeaderReady` might have turned `true`, the associated
304 // layout might not have been performed yet on the native side.
305 onLayout={() => {
0306 headerRef.current?.measure(
307 (_x: number, _y: number, _width: number, height: number) => {
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+ }
315 },
316 )
317 }}