Hide/show header and footer without re-renders, take two (#1849)
* Remove callsites using the state value * Remove unused code * Change shell mode without re-renders * Adjust "write your reply" for modezio/stable
parent
bd531f2344
commit
82059b7ee1
|
@ -1,60 +1,61 @@
|
||||||
import React from 'react'
|
|
||||||
import {autorun} from 'mobx'
|
|
||||||
import {
|
import {
|
||||||
Easing,
|
AnimatableValue,
|
||||||
interpolate,
|
interpolate,
|
||||||
useAnimatedStyle,
|
useAnimatedStyle,
|
||||||
useSharedValue,
|
|
||||||
withTiming,
|
withTiming,
|
||||||
|
Easing,
|
||||||
} from 'react-native-reanimated'
|
} from 'react-native-reanimated'
|
||||||
|
|
||||||
import {useMinimalShellMode as useMinimalShellModeState} from '#/state/shell/minimal-mode'
|
import {useMinimalShellMode as useMinimalShellModeState} from '#/state/shell/minimal-mode'
|
||||||
|
|
||||||
|
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 minimalShellMode = useMinimalShellModeState()
|
const mode = useMinimalShellModeState()
|
||||||
const minimalShellInterp = useSharedValue(0)
|
|
||||||
const footerMinimalShellTransform = useAnimatedStyle(() => {
|
const footerMinimalShellTransform = useAnimatedStyle(() => {
|
||||||
return {
|
return {
|
||||||
opacity: interpolate(minimalShellInterp.value, [0, 1], [1, 0]),
|
pointerEvents: mode.value ? 'none' : 'auto',
|
||||||
|
opacity: withShellTiming(interpolate(mode.value ? 1 : 0, [0, 1], [1, 0])),
|
||||||
transform: [
|
transform: [
|
||||||
{translateY: interpolate(minimalShellInterp.value, [0, 1], [0, 25])},
|
{
|
||||||
|
translateY: withShellTiming(
|
||||||
|
interpolate(mode.value ? 1 : 0, [0, 1], [0, 25]),
|
||||||
|
),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const headerMinimalShellTransform = useAnimatedStyle(() => {
|
const headerMinimalShellTransform = useAnimatedStyle(() => {
|
||||||
return {
|
return {
|
||||||
opacity: interpolate(minimalShellInterp.value, [0, 1], [1, 0]),
|
pointerEvents: mode.value ? 'none' : 'auto',
|
||||||
|
opacity: withShellTiming(interpolate(mode.value ? 1 : 0, [0, 1], [1, 0])),
|
||||||
transform: [
|
transform: [
|
||||||
{translateY: interpolate(minimalShellInterp.value, [0, 1], [0, -25])},
|
{
|
||||||
|
translateY: withShellTiming(
|
||||||
|
interpolate(mode.value ? 1 : 0, [0, 1], [0, -25]),
|
||||||
|
),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const fabMinimalShellTransform = useAnimatedStyle(() => {
|
const fabMinimalShellTransform = useAnimatedStyle(() => {
|
||||||
return {
|
return {
|
||||||
transform: [
|
transform: [
|
||||||
{translateY: interpolate(minimalShellInterp.value, [0, 1], [-44, 0])},
|
{
|
||||||
|
translateY: withShellTiming(
|
||||||
|
interpolate(mode.value ? 1 : 0, [0, 1], [-44, 0]),
|
||||||
|
),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
return autorun(() => {
|
|
||||||
if (minimalShellMode) {
|
|
||||||
minimalShellInterp.value = withTiming(1, {
|
|
||||||
duration: 125,
|
|
||||||
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
minimalShellInterp.value = withTiming(0, {
|
|
||||||
duration: 125,
|
|
||||||
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [minimalShellInterp, minimalShellMode])
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
minimalShellMode,
|
|
||||||
footerMinimalShellTransform,
|
footerMinimalShellTransform,
|
||||||
headerMinimalShellTransform,
|
headerMinimalShellTransform,
|
||||||
fabMinimalShellTransform,
|
fabMinimalShellTransform,
|
||||||
|
|
|
@ -33,9 +33,12 @@ export function useOnMainScroll(): [OnScrollCb, boolean, ResetCb] {
|
||||||
const dy = y - (lastY.current || 0)
|
const dy = y - (lastY.current || 0)
|
||||||
lastY.current = y
|
lastY.current = y
|
||||||
|
|
||||||
if (!minimalShellMode && dy > dyLimitDown && y > Y_LIMIT) {
|
if (!minimalShellMode.value && dy > dyLimitDown && y > Y_LIMIT) {
|
||||||
setMinimalShellMode(true)
|
setMinimalShellMode(true)
|
||||||
} else if (minimalShellMode && (dy < dyLimitUp * -1 || y <= Y_LIMIT)) {
|
} else if (
|
||||||
|
minimalShellMode.value &&
|
||||||
|
(dy < dyLimitUp * -1 || y <= Y_LIMIT)
|
||||||
|
) {
|
||||||
setMinimalShellMode(false)
|
setMinimalShellMode(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,28 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import {useSharedValue, SharedValue} from 'react-native-reanimated'
|
||||||
|
|
||||||
type StateContext = boolean
|
type StateContext = SharedValue<boolean>
|
||||||
type SetContext = (v: boolean) => void
|
type SetContext = (v: boolean) => void
|
||||||
|
|
||||||
const stateContext = React.createContext<StateContext>(false)
|
const stateContext = React.createContext<StateContext>({
|
||||||
|
value: false,
|
||||||
|
addListener() {},
|
||||||
|
removeListener() {},
|
||||||
|
modify() {},
|
||||||
|
})
|
||||||
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 [state, setState] = React.useState(false)
|
const mode = useSharedValue(false)
|
||||||
|
const setMode = React.useCallback(
|
||||||
|
(v: boolean) => {
|
||||||
|
mode.value = v
|
||||||
|
},
|
||||||
|
[mode],
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
<stateContext.Provider value={state}>
|
<stateContext.Provider value={mode}>
|
||||||
<setContext.Provider value={setState}>{children}</setContext.Provider>
|
<setContext.Provider value={setMode}>{children}</setContext.Provider>
|
||||||
</stateContext.Provider>
|
</stateContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,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 {minimalShellMode, headerMinimalShellTransform} = useMinimalShellMode()
|
const {headerMinimalShellTransform} = useMinimalShellMode()
|
||||||
|
|
||||||
const onPressAvi = React.useCallback(() => {
|
const onPressAvi = React.useCallback(() => {
|
||||||
setDrawerOpen(true)
|
setDrawerOpen(true)
|
||||||
|
@ -38,7 +38,6 @@ export const FeedsTabBar = observer(function FeedsTabBarImpl(
|
||||||
pal.border,
|
pal.border,
|
||||||
styles.tabBar,
|
styles.tabBar,
|
||||||
headerMinimalShellTransform,
|
headerMinimalShellTransform,
|
||||||
minimalShellMode && styles.disabled,
|
|
||||||
]}>
|
]}>
|
||||||
<View style={[pal.view, styles.topBar]}>
|
<View style={[pal.view, styles.topBar]}>
|
||||||
<View style={[pal.view]}>
|
<View style={[pal.view]}>
|
||||||
|
@ -110,7 +109,4 @@ const styles = StyleSheet.create({
|
||||||
title: {
|
title: {
|
||||||
fontSize: 21,
|
fontSize: 21,
|
||||||
},
|
},
|
||||||
disabled: {
|
|
||||||
pointerEvents: 'none',
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React, {useMemo} from 'react'
|
import React, {useMemo} from 'react'
|
||||||
import {InteractionManager, StyleSheet, View} from 'react-native'
|
import {InteractionManager, StyleSheet, View} from 'react-native'
|
||||||
|
import Animated from 'react-native-reanimated'
|
||||||
import {useFocusEffect} from '@react-navigation/native'
|
import {useFocusEffect} from '@react-navigation/native'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
|
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
|
||||||
|
@ -15,15 +16,14 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import {clamp} from 'lodash'
|
import {clamp} from 'lodash'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {useMinimalShellMode, useSetMinimalShellMode} from '#/state/shell'
|
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
|
||||||
|
import {useSetMinimalShellMode} from '#/state/shell'
|
||||||
const SHELL_FOOTER_HEIGHT = 44
|
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'>
|
||||||
export const PostThreadScreen = withAuthRequired(
|
export const PostThreadScreen = withAuthRequired(
|
||||||
observer(function PostThreadScreenImpl({route}: Props) {
|
observer(function PostThreadScreenImpl({route}: Props) {
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
const minimalShellMode = useMinimalShellMode()
|
const {fabMinimalShellTransform} = useMinimalShellMode()
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
const safeAreaInsets = useSafeAreaInsets()
|
const safeAreaInsets = useSafeAreaInsets()
|
||||||
const {name, rkey} = route.params
|
const {name, rkey} = route.params
|
||||||
|
@ -83,17 +83,17 @@ export const PostThreadScreen = withAuthRequired(
|
||||||
treeView={!!store.preferences.thread.lab_treeViewEnabled}
|
treeView={!!store.preferences.thread.lab_treeViewEnabled}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
{isMobile && !minimalShellMode && (
|
{isMobile && (
|
||||||
<View
|
<Animated.View
|
||||||
style={[
|
style={[
|
||||||
styles.prompt,
|
styles.prompt,
|
||||||
|
fabMinimalShellTransform,
|
||||||
{
|
{
|
||||||
bottom:
|
bottom: clamp(safeAreaInsets.bottom, 15, 30),
|
||||||
SHELL_FOOTER_HEIGHT + clamp(safeAreaInsets.bottom, 15, 30),
|
|
||||||
},
|
},
|
||||||
]}>
|
]}>
|
||||||
<ComposePrompt onPressCompose={onPressReply} />
|
<ComposePrompt onPressCompose={onPressReply} />
|
||||||
</View>
|
</Animated.View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
|
@ -39,7 +39,7 @@ export const BottomBar = observer(function BottomBarImpl({
|
||||||
const {isAtHome, isAtSearch, isAtFeeds, isAtNotifications, isAtMyProfile} =
|
const {isAtHome, isAtSearch, isAtFeeds, isAtNotifications, isAtMyProfile} =
|
||||||
useNavigationTabState()
|
useNavigationTabState()
|
||||||
|
|
||||||
const {minimalShellMode, footerMinimalShellTransform} = useMinimalShellMode()
|
const {footerMinimalShellTransform} = useMinimalShellMode()
|
||||||
const {notifications} = store.me
|
const {notifications} = store.me
|
||||||
|
|
||||||
const onPressTab = React.useCallback(
|
const onPressTab = React.useCallback(
|
||||||
|
@ -85,7 +85,6 @@ export const BottomBar = observer(function BottomBarImpl({
|
||||||
pal.border,
|
pal.border,
|
||||||
{paddingBottom: clamp(safeAreaInsets.bottom, 15, 30)},
|
{paddingBottom: clamp(safeAreaInsets.bottom, 15, 30)},
|
||||||
footerMinimalShellTransform,
|
footerMinimalShellTransform,
|
||||||
minimalShellMode && styles.disabled,
|
|
||||||
]}>
|
]}>
|
||||||
<Btn
|
<Btn
|
||||||
testID="bottomBarHomeBtn"
|
testID="bottomBarHomeBtn"
|
||||||
|
|
|
@ -65,7 +65,4 @@ export const styles = StyleSheet.create({
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderRadius: 100,
|
borderRadius: 100,
|
||||||
},
|
},
|
||||||
disabled: {
|
|
||||||
pointerEvents: 'none',
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue