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 mode
zio/stable
dan 2023-11-09 00:25:27 +00:00 committed by GitHub
parent bd531f2344
commit 82059b7ee1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 63 additions and 55 deletions

View File

@ -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,

View File

@ -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)
} }

View File

@ -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>
) )
} }

View File

@ -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',
},
}) })

View File

@ -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>
) )

View File

@ -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"

View File

@ -65,7 +65,4 @@ export const styles = StyleSheet.create({
borderWidth: 1, borderWidth: 1,
borderRadius: 100, borderRadius: 100,
}, },
disabled: {
pointerEvents: 'none',
},
}) })