[APP-1917] fix header measurement for android content disappearing on switch (#9964)
Co-authored-by: Samuel Newman <mozzius@protonmail.com>
This commit is contained in:
@@ -16,7 +16,6 @@ import Animated, {
|
|||||||
useSharedValue,
|
useSharedValue,
|
||||||
} from 'react-native-reanimated'
|
} from 'react-native-reanimated'
|
||||||
|
|
||||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
|
||||||
import {ScrollProvider} from '#/lib/ScrollContext'
|
import {ScrollProvider} from '#/lib/ScrollContext'
|
||||||
import {
|
import {
|
||||||
Pager,
|
Pager,
|
||||||
@@ -72,19 +71,19 @@ export function PagerWithHeader({
|
|||||||
const headerHeight = headerOnlyHeight + tabBarHeight
|
const headerHeight = headerOnlyHeight + tabBarHeight
|
||||||
|
|
||||||
// capture the header bar sizing
|
// capture the header bar sizing
|
||||||
const onTabBarLayout = useNonReactiveCallback((evt: LayoutChangeEvent) => {
|
const onTabBarLayout = useCallback((evt: LayoutChangeEvent) => {
|
||||||
const height = evt.nativeEvent.layout.height
|
const height = evt.nativeEvent.layout.height
|
||||||
if (height > 0) {
|
if (height > 0) {
|
||||||
// The rounding is necessary to prevent jumps on iOS
|
// The rounding is necessary to prevent jumps on iOS
|
||||||
setTabBarHeight(Math.round(height * 2) / 2)
|
setTabBarHeight(Math.round(height * 2) / 2)
|
||||||
}
|
}
|
||||||
})
|
}, [])
|
||||||
const onHeaderOnlyLayout = useNonReactiveCallback((height: number) => {
|
const onHeaderOnlyLayout = useCallback((height: number) => {
|
||||||
if (height > 0) {
|
if (height > 0) {
|
||||||
// The rounding is necessary to prevent jumps on iOS
|
// The rounding is necessary to prevent jumps on iOS
|
||||||
setHeaderOnlyHeight(Math.round(height * 2) / 2)
|
setHeaderOnlyHeight(Math.round(height * 2) / 2)
|
||||||
}
|
}
|
||||||
})
|
}, [])
|
||||||
|
|
||||||
const renderTabBar = useCallback(
|
const renderTabBar = useCallback(
|
||||||
(props: RenderTabBarFnProps) => {
|
(props: RenderTabBarFnProps) => {
|
||||||
@@ -270,7 +269,8 @@ let PagerTabBar = ({
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const headerRef = useRef(null)
|
const headerRef = useRef<View>(null)
|
||||||
|
const fallbackHeaderOnlyHeight = useRef(0)
|
||||||
return (
|
return (
|
||||||
<Animated.View
|
<Animated.View
|
||||||
pointerEvents={IS_IOS ? 'auto' : 'box-none'}
|
pointerEvents={IS_IOS ? 'auto' : 'box-none'}
|
||||||
@@ -278,7 +278,19 @@ let PagerTabBar = ({
|
|||||||
<View
|
<View
|
||||||
ref={headerRef}
|
ref={headerRef}
|
||||||
pointerEvents={IS_IOS ? 'auto' : 'box-none'}
|
pointerEvents={IS_IOS ? 'auto' : 'box-none'}
|
||||||
collapsable={false}>
|
collapsable={false}
|
||||||
|
onLayout={(e: LayoutChangeEvent) => {
|
||||||
|
// 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 = e.nativeEvent.layout.height
|
||||||
|
}
|
||||||
|
}}>
|
||||||
{renderHeader?.({setMinimumHeight: setMinimumHeaderHeight})}
|
{renderHeader?.({setMinimumHeight: setMinimumHeaderHeight})}
|
||||||
{
|
{
|
||||||
// It wouldn't be enough to place `onLayout` on the parent node because
|
// It wouldn't be enough to place `onLayout` on the parent node because
|
||||||
@@ -286,14 +298,20 @@ let PagerTabBar = ({
|
|||||||
// Instead, we'll render a brand node conditionally and get fresh layout.
|
// Instead, we'll render a brand node conditionally and get fresh layout.
|
||||||
isHeaderReady && (
|
isHeaderReady && (
|
||||||
<View
|
<View
|
||||||
|
collapsable={false}
|
||||||
// It wouldn't be enough to do this in a `ref` of an effect because,
|
// It wouldn't be enough to do this in a `ref` of an effect because,
|
||||||
// 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={() => {
|
||||||
// @ts-ignore
|
|
||||||
headerRef.current?.measure(
|
headerRef.current?.measure(
|
||||||
(_x: number, _y: number, _width: number, height: number) => {
|
(_x: number, _y: number, _width: number, height: number) => {
|
||||||
onHeaderOnlyLayout(height)
|
// 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)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user