Extract shell state into separate context (#1824)

* WIP

* Add shell state

* Integrate new shell state for drawer and minimal shell mode

* Replace isDrawerSwipeDisabled

* Split shell state into separate contexts to avoid needless re-renders

* Fix typo

---------

Co-authored-by: Paul Frazee <pfrazee@gmail.com>
This commit is contained in:
Eric Bailey 2023-11-07 13:37:47 -06:00 committed by GitHub
parent 7158157f5f
commit bfe196bac5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
51 changed files with 368 additions and 238 deletions

View file

@ -43,11 +43,13 @@ import {NavigationProp} from 'lib/routes/types'
import {useNavigationTabState} from 'lib/hooks/useNavigationTabState'
import {isWeb} from 'platform/detection'
import {formatCount, formatCountShortOnly} from 'view/com/util/numeric/format'
import {useSetDrawerOpen} from '#/state/shell'
export const DrawerContent = observer(function DrawerContentImpl() {
const theme = useTheme()
const pal = usePalette('default')
const store = useStores()
const setDrawerOpen = useSetDrawerOpen()
const navigation = useNavigation<NavigationProp>()
const {track} = useAnalytics()
const {isAtHome, isAtSearch, isAtFeeds, isAtNotifications, isAtMyProfile} =
@ -62,7 +64,7 @@ export const DrawerContent = observer(function DrawerContentImpl() {
(tab: string) => {
track('Menu:ItemClicked', {url: tab})
const state = navigation.getState()
store.shell.closeDrawer()
setDrawerOpen(false)
if (isWeb) {
// hack because we have flat navigator for web and MyProfile does not exist on the web navigator -ansh
if (tab === 'MyProfile') {
@ -83,7 +85,7 @@ export const DrawerContent = observer(function DrawerContentImpl() {
}
}
},
[store, track, navigation],
[store, track, navigation, setDrawerOpen],
)
const onPressHome = React.useCallback(() => onPressTab('Home'), [onPressTab])
@ -110,20 +112,20 @@ export const DrawerContent = observer(function DrawerContentImpl() {
const onPressLists = React.useCallback(() => {
track('Menu:ItemClicked', {url: 'Lists'})
navigation.navigate('Lists')
store.shell.closeDrawer()
}, [navigation, track, store.shell])
setDrawerOpen(false)
}, [navigation, track, setDrawerOpen])
const onPressModeration = React.useCallback(() => {
track('Menu:ItemClicked', {url: 'Moderation'})
navigation.navigate('Moderation')
store.shell.closeDrawer()
}, [navigation, track, store.shell])
setDrawerOpen(false)
}, [navigation, track, setDrawerOpen])
const onPressSettings = React.useCallback(() => {
track('Menu:ItemClicked', {url: 'Settings'})
navigation.navigate('Settings')
store.shell.closeDrawer()
}, [navigation, track, store.shell])
setDrawerOpen(false)
}, [navigation, track, setDrawerOpen])
const onPressFeedback = React.useCallback(() => {
track('Menu:FeedbackClicked')
@ -437,13 +439,14 @@ const InviteCodes = observer(function InviteCodesImpl({
}) {
const {track} = useAnalytics()
const store = useStores()
const setDrawerOpen = useSetDrawerOpen()
const pal = usePalette('default')
const {invitesAvailable} = store.me
const onPress = React.useCallback(() => {
track('Menu:ItemClicked', {url: '#invite-codes'})
store.shell.closeDrawer()
setDrawerOpen(false)
store.shell.openModal({name: 'invite-codes'})
}, [store, track])
}, [store, track, setDrawerOpen])
return (
<TouchableOpacity
testID="menuItemInviteCodes"

View file

@ -37,7 +37,7 @@ export const BottomBar = observer(function BottomBarImpl({
const {isAtHome, isAtSearch, isAtFeeds, isAtNotifications, isAtMyProfile} =
useNavigationTabState()
const {footerMinimalShellTransform} = useMinimalShellMode()
const {minimalShellMode, footerMinimalShellTransform} = useMinimalShellMode()
const {notifications} = store.me
const onPressTab = React.useCallback(
@ -83,7 +83,7 @@ export const BottomBar = observer(function BottomBarImpl({
pal.border,
{paddingBottom: clamp(safeAreaInsets.bottom, 15, 30)},
footerMinimalShellTransform,
store.shell.minimalShellMode && styles.disabled,
minimalShellMode && styles.disabled,
]}>
<Btn
testID="bottomBarHomeBtn"

View file

@ -6,6 +6,7 @@ import {
StyleSheet,
useWindowDimensions,
View,
BackHandler,
} from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {Drawer} from 'react-native-drawer-layout'
@ -18,7 +19,6 @@ import {DrawerContent} from './Drawer'
import {Composer} from './Composer'
import {useTheme} from 'lib/ThemeContext'
import {usePalette} from 'lib/hooks/usePalette'
import * as backHandler from 'lib/routes/back-handler'
import {RoutesContainer, TabsNavigator} from '../../Navigation'
import {isStateAtTabRoot} from 'lib/routes/helpers'
import {
@ -26,9 +26,18 @@ import {
initialWindowMetrics,
} from 'react-native-safe-area-context'
import {useOTAUpdate} from 'lib/hooks/useOTAUpdate'
import {
useIsDrawerOpen,
useSetDrawerOpen,
useIsDrawerSwipeDisabled,
} from '#/state/shell'
import {isAndroid} from 'platform/detection'
const ShellInner = observer(function ShellInnerImpl() {
const store = useStores()
const isDrawerOpen = useIsDrawerOpen()
const isDrawerSwipeDisabled = useIsDrawerSwipeDisabled()
const setIsDrawerOpen = useSetDrawerOpen()
useOTAUpdate() // this hook polls for OTA updates every few seconds
const winDim = useWindowDimensions()
const safeAreaInsets = useSafeAreaInsets()
@ -38,20 +47,26 @@ const ShellInner = observer(function ShellInnerImpl() {
)
const renderDrawerContent = React.useCallback(() => <DrawerContent />, [])
const onOpenDrawer = React.useCallback(
() => store.shell.openDrawer(),
[store],
() => setIsDrawerOpen(true),
[setIsDrawerOpen],
)
const onCloseDrawer = React.useCallback(
() => store.shell.closeDrawer(),
[store],
() => setIsDrawerOpen(false),
[setIsDrawerOpen],
)
const canGoBack = useNavigationState(state => !isStateAtTabRoot(state))
React.useEffect(() => {
const listener = backHandler.init(store)
return () => {
listener()
let listener = {remove() {}}
if (isAndroid) {
listener = BackHandler.addEventListener('hardwareBackPress', () => {
setIsDrawerOpen(false)
return store.shell.closeAnyActiveElement()
})
}
}, [store])
return () => {
listener.remove()
}
}, [store, setIsDrawerOpen])
return (
<>
@ -59,14 +74,12 @@ const ShellInner = observer(function ShellInnerImpl() {
<ErrorBoundary>
<Drawer
renderDrawerContent={renderDrawerContent}
open={store.shell.isDrawerOpen}
open={isDrawerOpen}
onOpen={onOpenDrawer}
onClose={onCloseDrawer}
swipeEdgeWidth={winDim.width / 2}
swipeEnabled={
!canGoBack &&
store.session.hasSession &&
!store.shell.isDrawerSwipeDisabled
!canGoBack && store.session.hasSession && !isDrawerSwipeDisabled
}>
<TabsNavigator />
</Drawer>

View file

@ -17,18 +17,22 @@ import {BottomBarWeb} from './bottom-bar/BottomBarWeb'
import {useNavigation} from '@react-navigation/native'
import {NavigationProp} from 'lib/routes/types'
import {useAuxClick} from 'lib/hooks/useAuxClick'
import {useIsDrawerOpen, useSetDrawerOpen} from '#/state/shell'
const ShellInner = observer(function ShellInnerImpl() {
const store = useStores()
const isDrawerOpen = useIsDrawerOpen()
const setDrawerOpen = useSetDrawerOpen()
const {isDesktop, isMobile} = useWebMediaQueries()
const navigator = useNavigation<NavigationProp>()
useAuxClick()
useEffect(() => {
navigator.addListener('state', () => {
setDrawerOpen(false)
store.shell.closeAnyActiveElement()
})
}, [navigator, store.shell])
}, [navigator, store.shell, setDrawerOpen])
const showBottomBar = isMobile && !store.onboarding.isActive
const showSideNavs =
@ -57,9 +61,9 @@ const ShellInner = observer(function ShellInnerImpl() {
{showBottomBar && <BottomBarWeb />}
<ModalsContainer />
<Lightbox />
{!isDesktop && store.shell.isDrawerOpen && (
{!isDesktop && isDrawerOpen && (
<TouchableOpacity
onPress={() => store.shell.closeDrawer()}
onPress={() => setDrawerOpen(false)}
style={styles.drawerMask}
accessibilityLabel="Close navigation footer"
accessibilityHint="Closes bottom navigation bar">