Sync top/bottom bar disappearance to the scroll (#1855)
* Disable existing code that toggles shell * Make shell mode a float * Translate based on the gesture * Track header and footer heights * Add web support * Fix types and cleanup * Add back isScrolled logic * Add comments
This commit is contained in:
parent
1dcf882619
commit
7a55ca6133
10 changed files with 183 additions and 107 deletions
|
@ -1,45 +1,29 @@
|
||||||
import {
|
import {interpolate, useAnimatedStyle} from 'react-native-reanimated'
|
||||||
AnimatableValue,
|
|
||||||
interpolate,
|
|
||||||
useAnimatedStyle,
|
|
||||||
withTiming,
|
|
||||||
Easing,
|
|
||||||
} from 'react-native-reanimated'
|
|
||||||
|
|
||||||
import {useMinimalShellMode as useMinimalShellModeState} from '#/state/shell/minimal-mode'
|
import {useMinimalShellMode as useMinimalShellModeState} from '#/state/shell/minimal-mode'
|
||||||
|
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||||
function withShellTiming<T extends AnimatableValue>(value: T): T {
|
|
||||||
'worklet'
|
|
||||||
return withTiming(value, {
|
|
||||||
duration: 125,
|
|
||||||
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useMinimalShellMode() {
|
export function useMinimalShellMode() {
|
||||||
const mode = useMinimalShellModeState()
|
const mode = useMinimalShellModeState()
|
||||||
|
const {footerHeight, headerHeight} = useShellLayout()
|
||||||
|
|
||||||
const footerMinimalShellTransform = useAnimatedStyle(() => {
|
const footerMinimalShellTransform = useAnimatedStyle(() => {
|
||||||
return {
|
return {
|
||||||
pointerEvents: mode.value ? 'none' : 'auto',
|
pointerEvents: mode.value === 0 ? 'auto' : 'none',
|
||||||
opacity: withShellTiming(interpolate(mode.value ? 1 : 0, [0, 1], [1, 0])),
|
opacity: Math.pow(1 - mode.value, 2),
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
translateY: withShellTiming(
|
translateY: interpolate(mode.value, [0, 1], [0, footerHeight.value]),
|
||||||
interpolate(mode.value ? 1 : 0, [0, 1], [0, 25]),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const headerMinimalShellTransform = useAnimatedStyle(() => {
|
const headerMinimalShellTransform = useAnimatedStyle(() => {
|
||||||
return {
|
return {
|
||||||
pointerEvents: mode.value ? 'none' : 'auto',
|
pointerEvents: mode.value === 0 ? 'auto' : 'none',
|
||||||
opacity: withShellTiming(interpolate(mode.value ? 1 : 0, [0, 1], [1, 0])),
|
opacity: Math.pow(1 - mode.value, 2),
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
translateY: withShellTiming(
|
translateY: interpolate(mode.value, [0, 1], [0, -headerHeight.value]),
|
||||||
interpolate(mode.value ? 1 : 0, [0, 1], [0, -25]),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@ -48,9 +32,7 @@ export function useMinimalShellMode() {
|
||||||
return {
|
return {
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
translateY: withShellTiming(
|
translateY: interpolate(mode.value, [0, 1], [-44, 0]),
|
||||||
interpolate(mode.value ? 1 : 0, [0, 1], [-44, 0]),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
import {useState, useCallback, useRef} from 'react'
|
import {useState, useCallback} from 'react'
|
||||||
import {NativeSyntheticEvent, NativeScrollEvent} from 'react-native'
|
import {NativeSyntheticEvent, NativeScrollEvent} from 'react-native'
|
||||||
import {s} from 'lib/styles'
|
|
||||||
import {useWebMediaQueries} from './useWebMediaQueries'
|
|
||||||
import {useSetMinimalShellMode, useMinimalShellMode} from '#/state/shell'
|
import {useSetMinimalShellMode, useMinimalShellMode} from '#/state/shell'
|
||||||
|
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||||
|
import {s} from 'lib/styles'
|
||||||
|
import {isWeb} from 'platform/detection'
|
||||||
|
import {
|
||||||
|
useAnimatedScrollHandler,
|
||||||
|
useSharedValue,
|
||||||
|
interpolate,
|
||||||
|
runOnJS,
|
||||||
|
} from 'react-native-reanimated'
|
||||||
|
|
||||||
const Y_LIMIT = 10
|
function clamp(num: number, min: number, max: number) {
|
||||||
|
'worklet'
|
||||||
const useDeviceLimits = () => {
|
return Math.min(Math.max(num, min), max)
|
||||||
const {isDesktop} = useWebMediaQueries()
|
|
||||||
return {
|
|
||||||
dyLimitUp: isDesktop ? 30 : 10,
|
|
||||||
dyLimitDown: isDesktop ? 150 : 10,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OnScrollCb = (
|
export type OnScrollCb = (
|
||||||
|
@ -20,53 +22,82 @@ export type OnScrollCb = (
|
||||||
export type ResetCb = () => void
|
export type ResetCb = () => void
|
||||||
|
|
||||||
export function useOnMainScroll(): [OnScrollCb, boolean, ResetCb] {
|
export function useOnMainScroll(): [OnScrollCb, boolean, ResetCb] {
|
||||||
let lastY = useRef(0)
|
const {headerHeight} = useShellLayout()
|
||||||
let [isScrolledDown, setIsScrolledDown] = useState(false)
|
const [isScrolledDown, setIsScrolledDown] = useState(false)
|
||||||
const {dyLimitUp, dyLimitDown} = useDeviceLimits()
|
const mode = useMinimalShellMode()
|
||||||
const minimalShellMode = useMinimalShellMode()
|
const setMode = useSetMinimalShellMode()
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const startDragOffset = useSharedValue<number | null>(null)
|
||||||
|
const startMode = useSharedValue<number | null>(null)
|
||||||
|
|
||||||
return [
|
const scrollHandler = useAnimatedScrollHandler({
|
||||||
useCallback(
|
onBeginDrag(e) {
|
||||||
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
startDragOffset.value = e.contentOffset.y
|
||||||
const y = event.nativeEvent.contentOffset.y
|
startMode.value = mode.value
|
||||||
const dy = y - (lastY.current || 0)
|
},
|
||||||
lastY.current = y
|
onEndDrag(e) {
|
||||||
|
startDragOffset.value = null
|
||||||
if (!minimalShellMode.value && dy > dyLimitDown && y > Y_LIMIT) {
|
startMode.value = null
|
||||||
setMinimalShellMode(true)
|
if (e.contentOffset.y < headerHeight.value / 2) {
|
||||||
} else if (
|
// If we're close to the top, show the shell.
|
||||||
minimalShellMode.value &&
|
setMode(false)
|
||||||
(dy < dyLimitUp * -1 || y <= Y_LIMIT)
|
} else {
|
||||||
) {
|
// Snap to whichever state is the closest.
|
||||||
setMinimalShellMode(false)
|
setMode(Math.round(mode.value) === 1)
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!isScrolledDown &&
|
|
||||||
event.nativeEvent.contentOffset.y > s.window.height
|
|
||||||
) {
|
|
||||||
setIsScrolledDown(true)
|
|
||||||
} else if (
|
|
||||||
isScrolledDown &&
|
|
||||||
event.nativeEvent.contentOffset.y < s.window.height
|
|
||||||
) {
|
|
||||||
setIsScrolledDown(false)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
onScroll(e) {
|
||||||
dyLimitDown,
|
// Keep track of whether we want to show "scroll to top".
|
||||||
dyLimitUp,
|
if (!isScrolledDown && e.contentOffset.y > s.window.height) {
|
||||||
isScrolledDown,
|
runOnJS(setIsScrolledDown)(true)
|
||||||
minimalShellMode,
|
} else if (isScrolledDown && e.contentOffset.y < s.window.height) {
|
||||||
setMinimalShellMode,
|
runOnJS(setIsScrolledDown)(false)
|
||||||
],
|
}
|
||||||
),
|
|
||||||
|
if (startDragOffset.value === null || startMode.value === null) {
|
||||||
|
if (mode.value !== 0 && e.contentOffset.y < headerHeight.value) {
|
||||||
|
// If we're close enough to the top, always show the shell.
|
||||||
|
// Even if we're not dragging.
|
||||||
|
setMode(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (isWeb) {
|
||||||
|
// On the web, there is no concept of "starting" the drag.
|
||||||
|
// When we get the first scroll event, we consider that the start.
|
||||||
|
startDragOffset.value = e.contentOffset.y
|
||||||
|
startMode.value = mode.value
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The "mode" value is always between 0 and 1.
|
||||||
|
// Figure out how much to move it based on the current dragged distance.
|
||||||
|
const dy = e.contentOffset.y - startDragOffset.value
|
||||||
|
const dProgress = interpolate(
|
||||||
|
dy,
|
||||||
|
[-headerHeight.value, headerHeight.value],
|
||||||
|
[-1, 1],
|
||||||
|
)
|
||||||
|
const newValue = clamp(startMode.value + dProgress, 0, 1)
|
||||||
|
if (newValue !== mode.value) {
|
||||||
|
// Manually adjust the value. This won't be (and shouldn't be) animated.
|
||||||
|
mode.value = newValue
|
||||||
|
}
|
||||||
|
if (isWeb) {
|
||||||
|
// On the web, there is no concept of "starting" the drag,
|
||||||
|
// so we don't have any specific anchor point to calculate the distance.
|
||||||
|
// Instead, update it continuosly along the way and diff with the last event.
|
||||||
|
startDragOffset.value = e.contentOffset.y
|
||||||
|
startMode.value = mode.value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return [
|
||||||
|
scrollHandler,
|
||||||
isScrolledDown,
|
isScrolledDown,
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
setIsScrolledDown(false)
|
setIsScrolledDown(false)
|
||||||
setMinimalShellMode(false)
|
setMode(false)
|
||||||
lastY.current = 1e8 // NOTE we set this very high so that the onScroll logic works right -prf
|
}, [setMode]),
|
||||||
}, [setIsScrolledDown, setMinimalShellMode]),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import {Provider as ShellLayoutProvder} from './shell-layout'
|
||||||
import {Provider as DrawerOpenProvider} from './drawer-open'
|
import {Provider as DrawerOpenProvider} from './drawer-open'
|
||||||
import {Provider as DrawerSwipableProvider} from './drawer-swipe-disabled'
|
import {Provider as DrawerSwipableProvider} from './drawer-swipe-disabled'
|
||||||
import {Provider as MinimalModeProvider} from './minimal-mode'
|
import {Provider as MinimalModeProvider} from './minimal-mode'
|
||||||
|
@ -16,6 +17,7 @@ export {useOnboardingState, useOnboardingDispatch} from './onboarding'
|
||||||
|
|
||||||
export function Provider({children}: React.PropsWithChildren<{}>) {
|
export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
return (
|
return (
|
||||||
|
<ShellLayoutProvder>
|
||||||
<DrawerOpenProvider>
|
<DrawerOpenProvider>
|
||||||
<DrawerSwipableProvider>
|
<DrawerSwipableProvider>
|
||||||
<MinimalModeProvider>
|
<MinimalModeProvider>
|
||||||
|
@ -25,5 +27,6 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
</MinimalModeProvider>
|
</MinimalModeProvider>
|
||||||
</DrawerSwipableProvider>
|
</DrawerSwipableProvider>
|
||||||
</DrawerOpenProvider>
|
</DrawerOpenProvider>
|
||||||
|
</ShellLayoutProvder>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useSharedValue, SharedValue} from 'react-native-reanimated'
|
import {
|
||||||
|
Easing,
|
||||||
|
SharedValue,
|
||||||
|
useSharedValue,
|
||||||
|
withTiming,
|
||||||
|
} from 'react-native-reanimated'
|
||||||
|
|
||||||
type StateContext = SharedValue<boolean>
|
type StateContext = SharedValue<number>
|
||||||
type SetContext = (v: boolean) => void
|
type SetContext = (v: boolean) => void
|
||||||
|
|
||||||
const stateContext = React.createContext<StateContext>({
|
const stateContext = React.createContext<StateContext>({
|
||||||
value: false,
|
value: 0,
|
||||||
addListener() {},
|
addListener() {},
|
||||||
removeListener() {},
|
removeListener() {},
|
||||||
modify() {},
|
modify() {},
|
||||||
|
@ -13,10 +18,14 @@ const stateContext = React.createContext<StateContext>({
|
||||||
const setContext = React.createContext<SetContext>((_: boolean) => {})
|
const setContext = React.createContext<SetContext>((_: boolean) => {})
|
||||||
|
|
||||||
export function Provider({children}: React.PropsWithChildren<{}>) {
|
export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
const mode = useSharedValue(false)
|
const mode = useSharedValue(0)
|
||||||
const setMode = React.useCallback(
|
const setMode = React.useCallback(
|
||||||
(v: boolean) => {
|
(v: boolean) => {
|
||||||
mode.value = v
|
'worklet'
|
||||||
|
mode.value = withTiming(v ? 1 : 0, {
|
||||||
|
duration: 400,
|
||||||
|
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
|
||||||
|
})
|
||||||
},
|
},
|
||||||
[mode],
|
[mode],
|
||||||
)
|
)
|
||||||
|
|
41
src/state/shell/shell-layout.tsx
Normal file
41
src/state/shell/shell-layout.tsx
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import React from 'react'
|
||||||
|
import {SharedValue, useSharedValue} from 'react-native-reanimated'
|
||||||
|
|
||||||
|
type StateContext = {
|
||||||
|
headerHeight: SharedValue<number>
|
||||||
|
footerHeight: SharedValue<number>
|
||||||
|
}
|
||||||
|
|
||||||
|
const stateContext = React.createContext<StateContext>({
|
||||||
|
headerHeight: {
|
||||||
|
value: 0,
|
||||||
|
addListener() {},
|
||||||
|
removeListener() {},
|
||||||
|
modify() {},
|
||||||
|
},
|
||||||
|
footerHeight: {
|
||||||
|
value: 0,
|
||||||
|
addListener() {},
|
||||||
|
removeListener() {},
|
||||||
|
modify() {},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
|
const headerHeight = useSharedValue(0)
|
||||||
|
const footerHeight = useSharedValue(0)
|
||||||
|
|
||||||
|
const value = React.useMemo(
|
||||||
|
() => ({
|
||||||
|
headerHeight,
|
||||||
|
footerHeight,
|
||||||
|
}),
|
||||||
|
[headerHeight, footerHeight],
|
||||||
|
)
|
||||||
|
|
||||||
|
return <stateContext.Provider value={value}>{children}</stateContext.Provider>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useShellLayout() {
|
||||||
|
return React.useContext(stateContext)
|
||||||
|
}
|
|
@ -182,7 +182,7 @@ export const FeedPage = observer(function FeedPageImpl({
|
||||||
feed={feed}
|
feed={feed}
|
||||||
scrollElRef={scrollElRef}
|
scrollElRef={scrollElRef}
|
||||||
onScroll={onMainScroll}
|
onScroll={onMainScroll}
|
||||||
scrollEventThrottle={100}
|
scrollEventThrottle={1}
|
||||||
renderEmptyState={renderEmptyState}
|
renderEmptyState={renderEmptyState}
|
||||||
renderEndOfFeed={renderEndOfFeed}
|
renderEndOfFeed={renderEndOfFeed}
|
||||||
ListHeaderComponent={ListHeaderComponent}
|
ListHeaderComponent={ListHeaderComponent}
|
||||||
|
|
|
@ -162,7 +162,7 @@ export const Feed = observer(function Feed({
|
||||||
onEndReached={onEndReached}
|
onEndReached={onEndReached}
|
||||||
onEndReachedThreshold={0.6}
|
onEndReachedThreshold={0.6}
|
||||||
onScroll={onScroll}
|
onScroll={onScroll}
|
||||||
scrollEventThrottle={100}
|
scrollEventThrottle={1}
|
||||||
contentContainerStyle={s.contentContainer}
|
contentContainerStyle={s.contentContainer}
|
||||||
// @ts-ignore our .web version only -prf
|
// @ts-ignore our .web version only -prf
|
||||||
desktopFixedHeight
|
desktopFixedHeight
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
import {FeedsTabBar as FeedsTabBarMobile} from './FeedsTabBarMobile'
|
import {FeedsTabBar as FeedsTabBarMobile} from './FeedsTabBarMobile'
|
||||||
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
|
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
|
||||||
|
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||||
|
|
||||||
export const FeedsTabBar = observer(function FeedsTabBarImpl(
|
export const FeedsTabBar = observer(function FeedsTabBarImpl(
|
||||||
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
|
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
|
||||||
|
@ -31,11 +32,15 @@ const FeedsTabBarTablet = observer(function FeedsTabBarTabletImpl(
|
||||||
const items = useHomeTabs(store.preferences.pinnedFeeds)
|
const items = useHomeTabs(store.preferences.pinnedFeeds)
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {headerMinimalShellTransform} = useMinimalShellMode()
|
const {headerMinimalShellTransform} = useMinimalShellMode()
|
||||||
|
const {headerHeight} = useShellLayout()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// @ts-ignore the type signature for transform wrong here, translateX and translateY need to be in separate objects -prf
|
// @ts-ignore the type signature for transform wrong here, translateX and translateY need to be in separate objects -prf
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={[pal.view, styles.tabBar, headerMinimalShellTransform]}>
|
style={[pal.view, styles.tabBar, headerMinimalShellTransform]}
|
||||||
|
onLayout={e => {
|
||||||
|
headerHeight.value = e.nativeEvent.layout.height
|
||||||
|
}}>
|
||||||
<TabBar
|
<TabBar
|
||||||
key={items.join(',')}
|
key={items.join(',')}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
|
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
|
||||||
import {useSetDrawerOpen} from '#/state/shell/drawer-open'
|
import {useSetDrawerOpen} from '#/state/shell/drawer-open'
|
||||||
|
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||||
|
|
||||||
export const FeedsTabBar = observer(function FeedsTabBarImpl(
|
export const FeedsTabBar = observer(function FeedsTabBarImpl(
|
||||||
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
|
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
|
||||||
|
@ -28,6 +29,7 @@ export const FeedsTabBar = observer(function FeedsTabBarImpl(
|
||||||
const setDrawerOpen = useSetDrawerOpen()
|
const setDrawerOpen = useSetDrawerOpen()
|
||||||
const items = useHomeTabs(store.preferences.pinnedFeeds)
|
const items = useHomeTabs(store.preferences.pinnedFeeds)
|
||||||
const brandBlue = useColorSchemeStyle(s.brandBlue, s.blue3)
|
const brandBlue = useColorSchemeStyle(s.brandBlue, s.blue3)
|
||||||
|
const {headerHeight} = useShellLayout()
|
||||||
const {headerMinimalShellTransform} = useMinimalShellMode()
|
const {headerMinimalShellTransform} = useMinimalShellMode()
|
||||||
|
|
||||||
const onPressAvi = React.useCallback(() => {
|
const onPressAvi = React.useCallback(() => {
|
||||||
|
@ -36,12 +38,10 @@ export const FeedsTabBar = observer(function FeedsTabBarImpl(
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={[
|
style={[pal.view, pal.border, styles.tabBar, headerMinimalShellTransform]}
|
||||||
pal.view,
|
onLayout={e => {
|
||||||
pal.border,
|
headerHeight.value = e.nativeEvent.layout.height
|
||||||
styles.tabBar,
|
}}>
|
||||||
headerMinimalShellTransform,
|
|
||||||
]}>
|
|
||||||
<View style={[pal.view, styles.topBar]}>
|
<View style={[pal.view, styles.topBar]}>
|
||||||
<View style={[pal.view]}>
|
<View style={[pal.view]}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
|
|
|
@ -27,6 +27,7 @@ import {UserAvatar} from 'view/com/util/UserAvatar'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
|
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||||
|
|
||||||
type TabOptions = 'Home' | 'Search' | 'Notifications' | 'MyProfile' | 'Feeds'
|
type TabOptions = 'Home' | 'Search' | 'Notifications' | 'MyProfile' | 'Feeds'
|
||||||
|
|
||||||
|
@ -39,6 +40,7 @@ export const BottomBar = observer(function BottomBarImpl({
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const safeAreaInsets = useSafeAreaInsets()
|
const safeAreaInsets = useSafeAreaInsets()
|
||||||
const {track} = useAnalytics()
|
const {track} = useAnalytics()
|
||||||
|
const {footerHeight} = useShellLayout()
|
||||||
const {isAtHome, isAtSearch, isAtFeeds, isAtNotifications, isAtMyProfile} =
|
const {isAtHome, isAtSearch, isAtFeeds, isAtNotifications, isAtMyProfile} =
|
||||||
useNavigationTabState()
|
useNavigationTabState()
|
||||||
|
|
||||||
|
@ -88,7 +90,10 @@ export const BottomBar = observer(function BottomBarImpl({
|
||||||
pal.border,
|
pal.border,
|
||||||
{paddingBottom: clamp(safeAreaInsets.bottom, 15, 30)},
|
{paddingBottom: clamp(safeAreaInsets.bottom, 15, 30)},
|
||||||
footerMinimalShellTransform,
|
footerMinimalShellTransform,
|
||||||
]}>
|
]}
|
||||||
|
onLayout={e => {
|
||||||
|
footerHeight.value = e.nativeEvent.layout.height
|
||||||
|
}}>
|
||||||
<Btn
|
<Btn
|
||||||
testID="bottomBarHomeBtn"
|
testID="bottomBarHomeBtn"
|
||||||
icon={
|
icon={
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue