my fork of the bluesky client

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>

authored by

Eric Bailey
Paul Frazee
and committed by
GitHub
bfe196ba 7158157f

+367 -237
+17 -14
src/App.native.tsx
··· 19 19 import * as Toast from 'view/com/util/Toast' 20 20 import {queryClient} from 'lib/react-query' 21 21 import {TestCtrls} from 'view/com/testing/TestCtrls' 22 + import {Provider as ShellStateProvider} from 'state/shell' 22 23 23 24 SplashScreen.preventAutoHideAsync() 24 25 ··· 44 45 return null 45 46 } 46 47 return ( 47 - <QueryClientProvider client={queryClient}> 48 - <ThemeProvider theme={rootStore.shell.colorMode}> 49 - <RootSiblingParent> 50 - <analytics.Provider> 51 - <RootStoreProvider value={rootStore}> 52 - <GestureHandlerRootView style={s.h100pct}> 53 - <TestCtrls /> 54 - <Shell /> 55 - </GestureHandlerRootView> 56 - </RootStoreProvider> 57 - </analytics.Provider> 58 - </RootSiblingParent> 59 - </ThemeProvider> 60 - </QueryClientProvider> 48 + <ShellStateProvider> 49 + <QueryClientProvider client={queryClient}> 50 + <ThemeProvider theme={rootStore.shell.colorMode}> 51 + <RootSiblingParent> 52 + <analytics.Provider> 53 + <RootStoreProvider value={rootStore}> 54 + <GestureHandlerRootView style={s.h100pct}> 55 + <TestCtrls /> 56 + <Shell /> 57 + </GestureHandlerRootView> 58 + </RootStoreProvider> 59 + </analytics.Provider> 60 + </RootSiblingParent> 61 + </ThemeProvider> 62 + </QueryClientProvider> 63 + </ShellStateProvider> 61 64 ) 62 65 }) 63 66
+17 -14
src/App.web.tsx
··· 14 14 import {ToastContainer} from 'view/com/util/Toast.web' 15 15 import {ThemeProvider} from 'lib/ThemeContext' 16 16 import {queryClient} from 'lib/react-query' 17 + import {Provider as ShellStateProvider} from 'state/shell' 17 18 18 19 const App = observer(function AppImpl() { 19 20 const [rootStore, setRootStore] = useState<RootStoreModel | undefined>( ··· 34 35 } 35 36 36 37 return ( 37 - <QueryClientProvider client={queryClient}> 38 - <ThemeProvider theme={rootStore.shell.colorMode}> 39 - <RootSiblingParent> 40 - <analytics.Provider> 41 - <RootStoreProvider value={rootStore}> 42 - <SafeAreaProvider> 43 - <Shell /> 44 - </SafeAreaProvider> 45 - <ToastContainer /> 46 - </RootStoreProvider> 47 - </analytics.Provider> 48 - </RootSiblingParent> 49 - </ThemeProvider> 50 - </QueryClientProvider> 38 + <ShellStateProvider> 39 + <QueryClientProvider client={queryClient}> 40 + <ThemeProvider theme={rootStore.shell.colorMode}> 41 + <RootSiblingParent> 42 + <analytics.Provider> 43 + <RootStoreProvider value={rootStore}> 44 + <SafeAreaProvider> 45 + <Shell /> 46 + </SafeAreaProvider> 47 + <ToastContainer /> 48 + </RootStoreProvider> 49 + </analytics.Provider> 50 + </RootSiblingParent> 51 + </ThemeProvider> 52 + </QueryClientProvider> 53 + </ShellStateProvider> 51 54 ) 52 55 }) 53 56
+4 -2
src/lib/hooks/useAccountSwitcher.ts
··· 6 6 import {AccountData} from 'state/models/session' 7 7 import {reset as resetNavigation} from '../../Navigation' 8 8 import * as Toast from 'view/com/util/Toast' 9 + import {useSetDrawerOpen} from '#/state/shell/drawer-open' 9 10 10 11 export function useAccountSwitcher(): [ 11 12 boolean, ··· 13 14 (acct: AccountData) => Promise<void>, 14 15 ] { 15 16 const {track} = useAnalytics() 16 - 17 17 const store = useStores() 18 + const setDrawerOpen = useSetDrawerOpen() 18 19 const [isSwitching, setIsSwitching] = useState(false) 19 20 const navigation = useNavigation<NavigationProp>() 20 21 ··· 23 24 track('Settings:SwitchAccountButtonClicked') 24 25 setIsSwitching(true) 25 26 const success = await store.session.resumeSession(acct) 27 + setDrawerOpen(false) 26 28 store.shell.closeAllActiveElements() 27 29 if (success) { 28 30 resetNavigation() ··· 34 36 store.session.clear() 35 37 } 36 38 }, 37 - [track, setIsSwitching, navigation, store], 39 + [track, setIsSwitching, navigation, store, setDrawerOpen], 38 40 ) 39 41 40 42 return [isSwitching, setIsSwitching, onPressSwitchAccount]
+6 -4
src/lib/hooks/useMinimalShellMode.tsx
··· 1 1 import React from 'react' 2 2 import {autorun} from 'mobx' 3 - import {useStores} from 'state/index' 4 3 import { 5 4 Easing, 6 5 interpolate, ··· 9 8 withTiming, 10 9 } from 'react-native-reanimated' 11 10 11 + import {useMinimalShellMode as useMinimalShellModeState} from '#/state/shell/minimal-mode' 12 + 12 13 export function useMinimalShellMode() { 13 - const store = useStores() 14 + const minimalShellMode = useMinimalShellModeState() 14 15 const minimalShellInterp = useSharedValue(0) 15 16 const footerMinimalShellTransform = useAnimatedStyle(() => { 16 17 return { ··· 38 39 39 40 React.useEffect(() => { 40 41 return autorun(() => { 41 - if (store.shell.minimalShellMode) { 42 + if (minimalShellMode) { 42 43 minimalShellInterp.value = withTiming(1, { 43 44 duration: 125, 44 45 easing: Easing.bezier(0.25, 0.1, 0.25, 1), ··· 50 51 }) 51 52 } 52 53 }) 53 - }, [minimalShellInterp, store.shell.minimalShellMode]) 54 + }, [minimalShellInterp, minimalShellMode]) 54 55 55 56 return { 57 + minimalShellMode, 56 58 footerMinimalShellTransform, 57 59 headerMinimalShellTransform, 58 60 fabMinimalShellTransform,
+17 -14
src/lib/hooks/useOnMainScroll.ts
··· 1 1 import {useState, useCallback, useRef} from 'react' 2 2 import {NativeSyntheticEvent, NativeScrollEvent} from 'react-native' 3 - import {RootStoreModel} from 'state/index' 4 3 import {s} from 'lib/styles' 5 4 import {useWebMediaQueries} from './useWebMediaQueries' 5 + import {useSetMinimalShellMode, useMinimalShellMode} from '#/state/shell' 6 6 7 7 const Y_LIMIT = 10 8 8 ··· 19 19 ) => void 20 20 export type ResetCb = () => void 21 21 22 - export function useOnMainScroll( 23 - store: RootStoreModel, 24 - ): [OnScrollCb, boolean, ResetCb] { 22 + export function useOnMainScroll(): [OnScrollCb, boolean, ResetCb] { 25 23 let lastY = useRef(0) 26 24 let [isScrolledDown, setIsScrolledDown] = useState(false) 27 25 const {dyLimitUp, dyLimitDown} = useDeviceLimits() 26 + const minimalShellMode = useMinimalShellMode() 27 + const setMinimalShellMode = useSetMinimalShellMode() 28 28 29 29 return [ 30 30 useCallback( ··· 33 33 const dy = y - (lastY.current || 0) 34 34 lastY.current = y 35 35 36 - if (!store.shell.minimalShellMode && dy > dyLimitDown && y > Y_LIMIT) { 37 - store.shell.setMinimalShellMode(true) 38 - } else if ( 39 - store.shell.minimalShellMode && 40 - (dy < dyLimitUp * -1 || y <= Y_LIMIT) 41 - ) { 42 - store.shell.setMinimalShellMode(false) 36 + if (!minimalShellMode && dy > dyLimitDown && y > Y_LIMIT) { 37 + setMinimalShellMode(true) 38 + } else if (minimalShellMode && (dy < dyLimitUp * -1 || y <= Y_LIMIT)) { 39 + setMinimalShellMode(false) 43 40 } 44 41 45 42 if ( ··· 54 51 setIsScrolledDown(false) 55 52 } 56 53 }, 57 - [store.shell, dyLimitDown, dyLimitUp, isScrolledDown], 54 + [ 55 + dyLimitDown, 56 + dyLimitUp, 57 + isScrolledDown, 58 + minimalShellMode, 59 + setMinimalShellMode, 60 + ], 58 61 ), 59 62 isScrolledDown, 60 63 useCallback(() => { 61 64 setIsScrolledDown(false) 62 - store.shell.setMinimalShellMode(false) 65 + setMinimalShellMode(false) 63 66 lastY.current = 1e8 // NOTE we set this very high so that the onScroll logic works right -prf 64 - }, [store, setIsScrolledDown]), 67 + }, [setIsScrolledDown, setMinimalShellMode]), 65 68 ] 66 69 }
-19
src/lib/routes/back-handler.ts
··· 1 - import {isAndroid} from 'platform/detection' 2 - import {BackHandler} from 'react-native' 3 - import {RootStoreModel} from 'state/index' 4 - 5 - export function init(store: RootStoreModel) { 6 - // only register back handler on android, otherwise it throws an error 7 - if (isAndroid) { 8 - const backHandler = BackHandler.addEventListener( 9 - 'hardwareBackPress', 10 - () => { 11 - return store.shell.closeAnyActiveElement() 12 - }, 13 - ) 14 - return () => { 15 - backHandler.remove() 16 - } 17 - } 18 - return () => {} 19 - }
-26
src/state/models/ui/shell.ts
··· 266 266 267 267 export class ShellUiModel { 268 268 colorMode: ColorMode = 'system' 269 - minimalShellMode = false 270 - isDrawerOpen = false 271 - isDrawerSwipeDisabled = false 272 269 isModalActive = false 273 270 activeModals: Modal[] = [] 274 271 isLightboxActive = false ··· 311 308 html.className = html.className.replace(/colorMode--\w+/g, '') 312 309 html.classList.add(`colorMode--${mode}`) 313 310 } 314 - } 315 - 316 - setMinimalShellMode(v: boolean) { 317 - this.minimalShellMode = v 318 311 } 319 312 320 313 /** ··· 334 327 this.closeComposer() 335 328 return true 336 329 } 337 - if (this.isDrawerOpen) { 338 - this.closeDrawer() 339 - return true 340 - } 341 330 return false 342 331 } 343 332 ··· 354 343 if (this.isComposerActive) { 355 344 this.closeComposer() 356 345 } 357 - if (this.isDrawerOpen) { 358 - this.closeDrawer() 359 - } 360 - } 361 - 362 - openDrawer() { 363 - this.isDrawerOpen = true 364 - } 365 - 366 - closeDrawer() { 367 - this.isDrawerOpen = false 368 - } 369 - 370 - setIsDrawerSwipeDisabled(v: boolean) { 371 - this.isDrawerSwipeDisabled = v 372 346 } 373 347 374 348 openModal(modal: Modal) {
+24
src/state/shell/drawer-open.tsx
··· 1 + import React from 'react' 2 + 3 + type StateContext = boolean 4 + type SetContext = (v: boolean) => void 5 + 6 + const stateContext = React.createContext<StateContext>(false) 7 + const setContext = React.createContext<SetContext>((_: boolean) => {}) 8 + 9 + export function Provider({children}: React.PropsWithChildren<{}>) { 10 + const [state, setState] = React.useState(false) 11 + return ( 12 + <stateContext.Provider value={state}> 13 + <setContext.Provider value={setState}>{children}</setContext.Provider> 14 + </stateContext.Provider> 15 + ) 16 + } 17 + 18 + export function useIsDrawerOpen() { 19 + return React.useContext(stateContext) 20 + } 21 + 22 + export function useSetDrawerOpen() { 23 + return React.useContext(setContext) 24 + }
+24
src/state/shell/drawer-swipe-disabled.tsx
··· 1 + import React from 'react' 2 + 3 + type StateContext = boolean 4 + type SetContext = (v: boolean) => void 5 + 6 + const stateContext = React.createContext<StateContext>(false) 7 + const setContext = React.createContext<SetContext>((_: boolean) => {}) 8 + 9 + export function Provider({children}: React.PropsWithChildren<{}>) { 10 + const [state, setState] = React.useState(false) 11 + return ( 12 + <stateContext.Provider value={state}> 13 + <setContext.Provider value={setState}>{children}</setContext.Provider> 14 + </stateContext.Provider> 15 + ) 16 + } 17 + 18 + export function useIsDrawerSwipeDisabled() { 19 + return React.useContext(stateContext) 20 + } 21 + 22 + export function useSetDrawerSwipeDisabled() { 23 + return React.useContext(setContext) 24 + }
+21
src/state/shell/index.tsx
··· 1 + import React from 'react' 2 + import {Provider as DrawerOpenProvider} from './drawer-open' 3 + import {Provider as DrawerSwipableProvider} from './drawer-swipe-disabled' 4 + import {Provider as MinimalModeProvider} from './minimal-mode' 5 + 6 + export {useIsDrawerOpen, useSetDrawerOpen} from './drawer-open' 7 + export { 8 + useIsDrawerSwipeDisabled, 9 + useSetDrawerSwipeDisabled, 10 + } from './drawer-swipe-disabled' 11 + export {useMinimalShellMode, useSetMinimalShellMode} from './minimal-mode' 12 + 13 + export function Provider({children}: React.PropsWithChildren<{}>) { 14 + return ( 15 + <DrawerOpenProvider> 16 + <DrawerSwipableProvider> 17 + <MinimalModeProvider>{children}</MinimalModeProvider> 18 + </DrawerSwipableProvider> 19 + </DrawerOpenProvider> 20 + ) 21 + }
+24
src/state/shell/minimal-mode.tsx
··· 1 + import React from 'react' 2 + 3 + type StateContext = boolean 4 + type SetContext = (v: boolean) => void 5 + 6 + const stateContext = React.createContext<StateContext>(false) 7 + const setContext = React.createContext<SetContext>((_: boolean) => {}) 8 + 9 + export function Provider({children}: React.PropsWithChildren<{}>) { 10 + const [state, setState] = React.useState(false) 11 + return ( 12 + <stateContext.Provider value={state}> 13 + <setContext.Provider value={setState}>{children}</setContext.Provider> 14 + </stateContext.Provider> 15 + ) 16 + } 17 + 18 + export function useMinimalShellMode() { 19 + return React.useContext(stateContext) 20 + } 21 + 22 + export function useSetMinimalShellMode() { 23 + return React.useContext(setContext) 24 + }
+4 -2
src/view/com/auth/LoggedOut.tsx
··· 9 9 import {useStores} from 'state/index' 10 10 import {useAnalytics} from 'lib/analytics/analytics' 11 11 import {SplashScreen} from './SplashScreen' 12 + import {useSetMinimalShellMode} from '#/state/shell/minimal-mode' 12 13 13 14 enum ScreenState { 14 15 S_LoginOrCreateAccount, ··· 19 20 export const LoggedOut = observer(function LoggedOutImpl() { 20 21 const pal = usePalette('default') 21 22 const store = useStores() 23 + const setMinimalShellMode = useSetMinimalShellMode() 22 24 const {screen} = useAnalytics() 23 25 const [screenState, setScreenState] = React.useState<ScreenState>( 24 26 ScreenState.S_LoginOrCreateAccount, ··· 26 28 27 29 React.useEffect(() => { 28 30 screen('Login') 29 - store.shell.setMinimalShellMode(true) 30 - }, [store, screen]) 31 + setMinimalShellMode(true) 32 + }, [screen, setMinimalShellMode]) 31 33 32 34 if ( 33 35 store.session.isResumingSession ||
+4 -2
src/view/com/auth/Onboarding.tsx
··· 8 8 import {Welcome} from './onboarding/Welcome' 9 9 import {RecommendedFeeds} from './onboarding/RecommendedFeeds' 10 10 import {RecommendedFollows} from './onboarding/RecommendedFollows' 11 + import {useSetMinimalShellMode} from '#/state/shell/minimal-mode' 11 12 12 13 export const Onboarding = observer(function OnboardingImpl() { 13 14 const pal = usePalette('default') 14 15 const store = useStores() 16 + const setMinimalShellMode = useSetMinimalShellMode() 15 17 16 18 React.useEffect(() => { 17 - store.shell.setMinimalShellMode(true) 18 - }, [store]) 19 + setMinimalShellMode(true) 20 + }, [setMinimalShellMode]) 19 21 20 22 const next = () => store.onboarding.next() 21 23 const skip = () => store.onboarding.skip()
+1 -1
src/view/com/feeds/FeedPage.tsx
··· 38 38 const store = useStores() 39 39 const pal = usePalette('default') 40 40 const {isDesktop} = useWebMediaQueries() 41 - const [onMainScroll, isScrolledDown, resetMainScroll] = useOnMainScroll(store) 41 + const [onMainScroll, isScrolledDown, resetMainScroll] = useOnMainScroll() 42 42 const {screen, track} = useAnalytics() 43 43 const headerOffset = useHeaderOffset() 44 44 const scrollElRef = React.useRef<FlatList>(null)
+7 -5
src/view/com/pager/FeedsTabBarMobile.tsx
··· 13 13 import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome' 14 14 import {s} from 'lib/styles' 15 15 import {HITSLOP_10} from 'lib/constants' 16 + import Animated from 'react-native-reanimated' 16 17 import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' 17 - import Animated from 'react-native-reanimated' 18 + import {useSetDrawerOpen} from '#/state/shell/drawer-open' 18 19 19 20 export const FeedsTabBar = observer(function FeedsTabBarImpl( 20 21 props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void}, 21 22 ) { 22 23 const pal = usePalette('default') 23 24 const store = useStores() 25 + const setDrawerOpen = useSetDrawerOpen() 24 26 const items = useHomeTabs(store.preferences.pinnedFeeds) 25 27 const brandBlue = useColorSchemeStyle(s.brandBlue, s.blue3) 26 - const {headerMinimalShellTransform} = useMinimalShellMode() 28 + const {minimalShellMode, headerMinimalShellTransform} = useMinimalShellMode() 27 29 28 30 const onPressAvi = React.useCallback(() => { 29 - store.shell.openDrawer() 30 - }, [store]) 31 + setDrawerOpen(true) 32 + }, [setDrawerOpen]) 31 33 32 34 return ( 33 35 <Animated.View ··· 36 38 pal.border, 37 39 styles.tabBar, 38 40 headerMinimalShellTransform, 39 - store.shell.minimalShellMode && styles.disabled, 41 + minimalShellMode && styles.disabled, 40 42 ]}> 41 43 <View style={[pal.view, styles.topBar]}> 42 44 <View style={[pal.view]}>
+4 -2
src/view/com/profile/ProfileSubpageHeader.tsx
··· 17 17 import {BACK_HITSLOP} from 'lib/constants' 18 18 import {isNative} from 'platform/detection' 19 19 import {ImagesLightbox} from 'state/models/ui/shell' 20 + import {useSetDrawerOpen} from '#/state/shell' 20 21 21 22 export const ProfileSubpageHeader = observer(function HeaderImpl({ 22 23 isLoading, ··· 42 43 avatarType: UserAvatarType 43 44 }>) { 44 45 const store = useStores() 46 + const setDrawerOpen = useSetDrawerOpen() 45 47 const navigation = useNavigation<NavigationProp>() 46 48 const {isMobile} = useWebMediaQueries() 47 49 const pal = usePalette('default') ··· 56 58 }, [navigation]) 57 59 58 60 const onPressMenu = React.useCallback(() => { 59 - store.shell.openDrawer() 60 - }, [store]) 61 + setDrawerOpen(true) 62 + }, [setDrawerOpen]) 61 63 62 64 const onPressAvi = React.useCallback(() => { 63 65 if (
+4 -4
src/view/com/search/HeaderWithInput.tsx
··· 8 8 import {MagnifyingGlassIcon} from 'lib/icons' 9 9 import {useTheme} from 'lib/ThemeContext' 10 10 import {usePalette} from 'lib/hooks/usePalette' 11 - import {useStores} from 'state/index' 12 11 import {useAnalytics} from 'lib/analytics/analytics' 13 12 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 14 13 import {HITSLOP_10} from 'lib/constants' 14 + import {useSetDrawerOpen} from '#/state/shell' 15 15 16 16 interface Props { 17 17 isInputFocused: boolean ··· 33 33 onSubmitQuery, 34 34 showMenu = true, 35 35 }: Props) { 36 - const store = useStores() 36 + const setDrawerOpen = useSetDrawerOpen() 37 37 const theme = useTheme() 38 38 const pal = usePalette('default') 39 39 const {track} = useAnalytics() ··· 42 42 43 43 const onPressMenu = React.useCallback(() => { 44 44 track('ViewHeader:MenuButtonClicked') 45 - store.shell.openDrawer() 46 - }, [track, store]) 45 + setDrawerOpen(true) 46 + }, [track, setDrawerOpen]) 47 47 48 48 const onPressCancelSearchInner = React.useCallback(() => { 49 49 onPressCancelSearch()
+4 -4
src/view/com/util/SimpleViewHeader.tsx
··· 10 10 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 11 11 import {useNavigation} from '@react-navigation/native' 12 12 import {CenteredView} from './Views' 13 - import {useStores} from 'state/index' 14 13 import {usePalette} from 'lib/hooks/usePalette' 15 14 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 16 15 import {useAnalytics} from 'lib/analytics/analytics' 17 16 import {NavigationProp} from 'lib/routes/types' 17 + import {useSetDrawerOpen} from '#/state/shell' 18 18 19 19 const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20} 20 20 ··· 27 27 style?: StyleProp<ViewStyle> 28 28 }>) { 29 29 const pal = usePalette('default') 30 - const store = useStores() 30 + const setDrawerOpen = useSetDrawerOpen() 31 31 const navigation = useNavigation<NavigationProp>() 32 32 const {track} = useAnalytics() 33 33 const {isMobile} = useWebMediaQueries() ··· 43 43 44 44 const onPressMenu = React.useCallback(() => { 45 45 track('ViewHeader:MenuButtonClicked') 46 - store.shell.openDrawer() 47 - }, [track, store]) 46 + setDrawerOpen(true) 47 + }, [track, setDrawerOpen]) 48 48 49 49 const Container = isMobile ? View : CenteredView 50 50 return (
+4 -4
src/view/com/util/ViewHeader.tsx
··· 5 5 import {useNavigation} from '@react-navigation/native' 6 6 import {CenteredView} from './Views' 7 7 import {Text} from './text/Text' 8 - import {useStores} from 'state/index' 9 8 import {usePalette} from 'lib/hooks/usePalette' 10 9 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 11 10 import {useAnalytics} from 'lib/analytics/analytics' 12 11 import {NavigationProp} from 'lib/routes/types' 13 12 import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' 14 13 import Animated from 'react-native-reanimated' 14 + import {useSetDrawerOpen} from '#/state/shell' 15 15 16 16 const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20} 17 17 ··· 33 33 renderButton?: () => JSX.Element 34 34 }) { 35 35 const pal = usePalette('default') 36 - const store = useStores() 36 + const setDrawerOpen = useSetDrawerOpen() 37 37 const navigation = useNavigation<NavigationProp>() 38 38 const {track} = useAnalytics() 39 39 const {isDesktop, isTablet} = useWebMediaQueries() ··· 48 48 49 49 const onPressMenu = React.useCallback(() => { 50 50 track('ViewHeader:MenuButtonClicked') 51 - store.shell.openDrawer() 52 - }, [track, store]) 51 + setDrawerOpen(true) 52 + }, [track, setDrawerOpen]) 53 53 54 54 if (isDesktop) { 55 55 if (showOnDesktop) {
+4 -2
src/view/screens/AppPasswords.tsx
··· 16 16 import {useFocusEffect} from '@react-navigation/native' 17 17 import {ViewHeader} from '../com/util/ViewHeader' 18 18 import {CenteredView} from 'view/com/util/Views' 19 + import {useSetMinimalShellMode} from '#/state/shell' 19 20 20 21 type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppPasswords'> 21 22 export const AppPasswords = withAuthRequired( 22 23 observer(function AppPasswordsImpl({}: Props) { 23 24 const pal = usePalette('default') 24 25 const store = useStores() 26 + const setMinimalShellMode = useSetMinimalShellMode() 25 27 const {screen} = useAnalytics() 26 28 const {isTabletOrDesktop} = useWebMediaQueries() 27 29 28 30 useFocusEffect( 29 31 React.useCallback(() => { 30 32 screen('AppPasswords') 31 - store.shell.setMinimalShellMode(false) 32 - }, [screen, store]), 33 + setMinimalShellMode(false) 34 + }, [screen, setMinimalShellMode]), 33 35 ) 34 36 35 37 const onAdd = React.useCallback(async () => {
+4 -4
src/view/screens/CommunityGuidelines.tsx
··· 5 5 import {TextLink} from 'view/com/util/Link' 6 6 import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' 7 7 import {ViewHeader} from '../com/util/ViewHeader' 8 - import {useStores} from 'state/index' 9 8 import {ScrollView} from 'view/com/util/Views' 10 9 import {usePalette} from 'lib/hooks/usePalette' 11 10 import {s} from 'lib/styles' 11 + import {useSetMinimalShellMode} from '#/state/shell' 12 12 13 13 type Props = NativeStackScreenProps< 14 14 CommonNavigatorParams, ··· 16 16 > 17 17 export const CommunityGuidelinesScreen = (_props: Props) => { 18 18 const pal = usePalette('default') 19 - const store = useStores() 19 + const setMinimalShellMode = useSetMinimalShellMode() 20 20 21 21 useFocusEffect( 22 22 React.useCallback(() => { 23 - store.shell.setMinimalShellMode(false) 24 - }, [store]), 23 + setMinimalShellMode(false) 24 + }, [setMinimalShellMode]), 25 25 ) 26 26 27 27 return (
+4 -4
src/view/screens/CopyrightPolicy.tsx
··· 5 5 import {TextLink} from 'view/com/util/Link' 6 6 import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' 7 7 import {ViewHeader} from '../com/util/ViewHeader' 8 - import {useStores} from 'state/index' 9 8 import {ScrollView} from 'view/com/util/Views' 10 9 import {usePalette} from 'lib/hooks/usePalette' 11 10 import {s} from 'lib/styles' 11 + import {useSetMinimalShellMode} from '#/state/shell' 12 12 13 13 type Props = NativeStackScreenProps<CommonNavigatorParams, 'CopyrightPolicy'> 14 14 export const CopyrightPolicyScreen = (_props: Props) => { 15 15 const pal = usePalette('default') 16 - const store = useStores() 16 + const setMinimalShellMode = useSetMinimalShellMode() 17 17 18 18 useFocusEffect( 19 19 React.useCallback(() => { 20 - store.shell.setMinimalShellMode(false) 21 - }, [store]), 20 + setMinimalShellMode(false) 21 + }, [setMinimalShellMode]), 22 22 ) 23 23 24 24 return (
+4 -2
src/view/screens/Feeds.tsx
··· 27 27 import {FlatList} from 'view/com/util/Views' 28 28 import {useFocusEffect} from '@react-navigation/native' 29 29 import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard' 30 + import {useSetMinimalShellMode} from '#/state/shell' 30 31 31 32 type Props = NativeStackScreenProps<FeedsTabNavigatorParams, 'Feeds'> 32 33 export const FeedsScreen = withAuthRequired( 33 34 observer<Props>(function FeedsScreenImpl({}: Props) { 34 35 const pal = usePalette('default') 35 36 const store = useStores() 37 + const setMinimalShellMode = useSetMinimalShellMode() 36 38 const {isMobile, isTabletOrDesktop} = useWebMediaQueries() 37 39 const myFeeds = store.me.myFeeds 38 40 const [query, setQuery] = React.useState<string>('') ··· 43 45 44 46 useFocusEffect( 45 47 React.useCallback(() => { 46 - store.shell.setMinimalShellMode(false) 48 + setMinimalShellMode(false) 47 49 myFeeds.setup() 48 50 49 51 const softResetSub = store.onScreenSoftReset(() => myFeeds.refresh()) 50 52 return () => { 51 53 softResetSub.remove() 52 54 } 53 - }, [store, myFeeds]), 55 + }, [store, myFeeds, setMinimalShellMode]), 54 56 ) 55 57 React.useEffect(() => { 56 58 // watch for changes to saved/pinned feeds
+10 -7
src/view/screens/Home.tsx
··· 14 14 import {useStores} from 'state/index' 15 15 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 16 16 import {FeedPage} from 'view/com/feeds/FeedPage' 17 + import {useSetMinimalShellMode, useSetDrawerSwipeDisabled} from '#/state/shell' 17 18 18 19 export const POLL_FREQ = 30e3 // 30sec 19 20 ··· 21 22 export const HomeScreen = withAuthRequired( 22 23 observer(function HomeScreenImpl({}: Props) { 23 24 const store = useStores() 25 + const setMinimalShellMode = useSetMinimalShellMode() 26 + const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled() 24 27 const pagerRef = React.useRef<PagerRef>(null) 25 28 const [selectedPage, setSelectedPage] = React.useState(0) 26 29 const [customFeeds, setCustomFeeds] = React.useState<PostsFeedModel[]>([]) ··· 61 64 62 65 useFocusEffect( 63 66 React.useCallback(() => { 64 - store.shell.setMinimalShellMode(false) 65 - store.shell.setIsDrawerSwipeDisabled(selectedPage > 0) 67 + setMinimalShellMode(false) 68 + setDrawerSwipeDisabled(selectedPage > 0) 66 69 return () => { 67 - store.shell.setIsDrawerSwipeDisabled(false) 70 + setDrawerSwipeDisabled(false) 68 71 } 69 - }, [store, selectedPage]), 72 + }, [setDrawerSwipeDisabled, selectedPage, setMinimalShellMode]), 70 73 ) 71 74 72 75 const onPageSelected = React.useCallback( 73 76 (index: number) => { 74 - store.shell.setMinimalShellMode(false) 77 + setMinimalShellMode(false) 75 78 setSelectedPage(index) 76 - store.shell.setIsDrawerSwipeDisabled(index > 0) 79 + setDrawerSwipeDisabled(index > 0) 77 80 }, 78 - [store, setSelectedPage], 81 + [setDrawerSwipeDisabled, setSelectedPage, setMinimalShellMode], 79 82 ) 80 83 81 84 const onPressSelected = React.useCallback(() => {
+4 -2
src/view/screens/LanguageSettings.tsx
··· 18 18 import {useFocusEffect} from '@react-navigation/native' 19 19 import {LANGUAGES} from 'lib/../locale/languages' 20 20 import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select' 21 + import {useSetMinimalShellMode} from '#/state/shell' 21 22 22 23 type Props = NativeStackScreenProps<CommonNavigatorParams, 'LanguageSettings'> 23 24 ··· 28 29 const store = useStores() 29 30 const {isTabletOrDesktop} = useWebMediaQueries() 30 31 const {screen, track} = useAnalytics() 32 + const setMinimalShellMode = useSetMinimalShellMode() 31 33 32 34 useFocusEffect( 33 35 React.useCallback(() => { 34 36 screen('Settings') 35 - store.shell.setMinimalShellMode(false) 36 - }, [screen, store]), 37 + setMinimalShellMode(false) 38 + }, [screen, setMinimalShellMode]), 37 39 ) 38 40 39 41 const onPressContentLanguages = React.useCallback(() => {
+4 -2
src/view/screens/Lists.tsx
··· 16 16 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 17 17 import {SimpleViewHeader} from 'view/com/util/SimpleViewHeader' 18 18 import {s} from 'lib/styles' 19 + import {useSetMinimalShellMode} from '#/state/shell' 19 20 20 21 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Lists'> 21 22 export const ListsScreen = withAuthRequired( 22 23 observer(function ListsScreenImpl({}: Props) { 23 24 const pal = usePalette('default') 24 25 const store = useStores() 26 + const setMinimalShellMode = useSetMinimalShellMode() 25 27 const {isMobile} = useWebMediaQueries() 26 28 const navigation = useNavigation<NavigationProp>() 27 29 ··· 32 34 33 35 useFocusEffect( 34 36 React.useCallback(() => { 35 - store.shell.setMinimalShellMode(false) 37 + setMinimalShellMode(false) 36 38 listsLists.refresh() 37 - }, [store, listsLists]), 39 + }, [listsLists, setMinimalShellMode]), 38 40 ) 39 41 40 42 const onPressNewList = React.useCallback(() => {
+4 -4
src/view/screens/Log.tsx
··· 5 5 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 6 6 import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' 7 7 import {ScrollView} from '../com/util/Views' 8 - import {useStores} from 'state/index' 9 8 import {s} from 'lib/styles' 10 9 import {ViewHeader} from '../com/util/ViewHeader' 11 10 import {Text} from '../com/util/text/Text' 12 11 import {usePalette} from 'lib/hooks/usePalette' 13 12 import {getEntries} from '#/logger/logDump' 14 13 import {ago} from 'lib/strings/time' 14 + import {useSetMinimalShellMode} from '#/state/shell' 15 15 16 16 export const LogScreen = observer(function Log({}: NativeStackScreenProps< 17 17 CommonNavigatorParams, 18 18 'Log' 19 19 >) { 20 20 const pal = usePalette('default') 21 - const store = useStores() 21 + const setMinimalShellMode = useSetMinimalShellMode() 22 22 const [expanded, setExpanded] = React.useState<string[]>([]) 23 23 24 24 useFocusEffect( 25 25 React.useCallback(() => { 26 - store.shell.setMinimalShellMode(false) 27 - }, [store]), 26 + setMinimalShellMode(false) 27 + }, [setMinimalShellMode]), 28 28 ) 29 29 30 30 const toggler = (id: string) => () => {
+4 -2
src/view/screens/Moderation.tsx
··· 17 17 import {usePalette} from 'lib/hooks/usePalette' 18 18 import {useAnalytics} from 'lib/analytics/analytics' 19 19 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 20 + import {useSetMinimalShellMode} from '#/state/shell' 20 21 21 22 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Moderation'> 22 23 export const ModerationScreen = withAuthRequired( 23 24 observer(function Moderation({}: Props) { 24 25 const pal = usePalette('default') 25 26 const store = useStores() 27 + const setMinimalShellMode = useSetMinimalShellMode() 26 28 const {screen, track} = useAnalytics() 27 29 const {isTabletOrDesktop} = useWebMediaQueries() 28 30 29 31 useFocusEffect( 30 32 React.useCallback(() => { 31 33 screen('Moderation') 32 - store.shell.setMinimalShellMode(false) 33 - }, [screen, store]), 34 + setMinimalShellMode(false) 35 + }, [screen, setMinimalShellMode]), 34 36 ) 35 37 36 38 const onPressContentFiltering = React.useCallback(() => {
+4 -2
src/view/screens/ModerationBlockedAccounts.tsx
··· 22 22 import {CenteredView} from 'view/com/util/Views' 23 23 import {ProfileCard} from 'view/com/profile/ProfileCard' 24 24 import {logger} from '#/logger' 25 + import {useSetMinimalShellMode} from '#/state/shell' 25 26 26 27 type Props = NativeStackScreenProps< 27 28 CommonNavigatorParams, ··· 31 32 observer(function ModerationBlockedAccountsImpl({}: Props) { 32 33 const pal = usePalette('default') 33 34 const store = useStores() 35 + const setMinimalShellMode = useSetMinimalShellMode() 34 36 const {isTabletOrDesktop} = useWebMediaQueries() 35 37 const {screen} = useAnalytics() 36 38 const blockedAccounts = useMemo( ··· 41 43 useFocusEffect( 42 44 React.useCallback(() => { 43 45 screen('BlockedAccounts') 44 - store.shell.setMinimalShellMode(false) 46 + setMinimalShellMode(false) 45 47 blockedAccounts.refresh() 46 - }, [screen, store, blockedAccounts]), 48 + }, [screen, setMinimalShellMode, blockedAccounts]), 47 49 ) 48 50 49 51 const onRefresh = React.useCallback(() => {
+4 -2
src/view/screens/ModerationModlists.tsx
··· 16 16 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 17 17 import {SimpleViewHeader} from 'view/com/util/SimpleViewHeader' 18 18 import {s} from 'lib/styles' 19 + import {useSetMinimalShellMode} from '#/state/shell' 19 20 20 21 type Props = NativeStackScreenProps<CommonNavigatorParams, 'ModerationModlists'> 21 22 export const ModerationModlistsScreen = withAuthRequired( 22 23 observer(function ModerationModlistsScreenImpl({}: Props) { 23 24 const pal = usePalette('default') 24 25 const store = useStores() 26 + const setMinimalShellMode = useSetMinimalShellMode() 25 27 const {isMobile} = useWebMediaQueries() 26 28 const navigation = useNavigation<NavigationProp>() 27 29 ··· 32 34 33 35 useFocusEffect( 34 36 React.useCallback(() => { 35 - store.shell.setMinimalShellMode(false) 37 + setMinimalShellMode(false) 36 38 mutelists.refresh() 37 - }, [store, mutelists]), 39 + }, [mutelists, setMinimalShellMode]), 38 40 ) 39 41 40 42 const onPressNewList = React.useCallback(() => {
+4 -2
src/view/screens/ModerationMutedAccounts.tsx
··· 22 22 import {CenteredView} from 'view/com/util/Views' 23 23 import {ProfileCard} from 'view/com/profile/ProfileCard' 24 24 import {logger} from '#/logger' 25 + import {useSetMinimalShellMode} from '#/state/shell' 25 26 26 27 type Props = NativeStackScreenProps< 27 28 CommonNavigatorParams, ··· 31 32 observer(function ModerationMutedAccountsImpl({}: Props) { 32 33 const pal = usePalette('default') 33 34 const store = useStores() 35 + const setMinimalShellMode = useSetMinimalShellMode() 34 36 const {isTabletOrDesktop} = useWebMediaQueries() 35 37 const {screen} = useAnalytics() 36 38 const mutedAccounts = useMemo(() => new MutedAccountsModel(store), [store]) ··· 38 40 useFocusEffect( 39 41 React.useCallback(() => { 40 42 screen('MutedAccounts') 41 - store.shell.setMinimalShellMode(false) 43 + setMinimalShellMode(false) 42 44 mutedAccounts.refresh() 43 - }, [screen, store, mutedAccounts]), 45 + }, [screen, setMinimalShellMode, mutedAccounts]), 44 46 ) 45 47 46 48 const onRefresh = React.useCallback(() => {
+4 -4
src/view/screens/NotFound.tsx
··· 10 10 import {Button} from 'view/com/util/forms/Button' 11 11 import {NavigationProp} from 'lib/routes/types' 12 12 import {usePalette} from 'lib/hooks/usePalette' 13 - import {useStores} from 'state/index' 14 13 import {s} from 'lib/styles' 14 + import {useSetMinimalShellMode} from '#/state/shell' 15 15 16 16 export const NotFoundScreen = () => { 17 17 const pal = usePalette('default') 18 18 const navigation = useNavigation<NavigationProp>() 19 - const store = useStores() 19 + const setMinimalShellMode = useSetMinimalShellMode() 20 20 21 21 useFocusEffect( 22 22 React.useCallback(() => { 23 - store.shell.setMinimalShellMode(false) 24 - }, [store]), 23 + setMinimalShellMode(false) 24 + }, [setMinimalShellMode]), 25 25 ) 26 26 27 27 const canGoBack = navigation.canGoBack()
+5 -4
src/view/screens/Notifications.tsx
··· 21 21 import {useAnalytics} from 'lib/analytics/analytics' 22 22 import {isWeb} from 'platform/detection' 23 23 import {logger} from '#/logger' 24 + import {useSetMinimalShellMode} from '#/state/shell' 24 25 25 26 type Props = NativeStackScreenProps< 26 27 NotificationsTabNavigatorParams, ··· 29 30 export const NotificationsScreen = withAuthRequired( 30 31 observer(function NotificationsScreenImpl({}: Props) { 31 32 const store = useStores() 32 - const [onMainScroll, isScrolledDown, resetMainScroll] = 33 - useOnMainScroll(store) 33 + const setMinimalShellMode = useSetMinimalShellMode() 34 + const [onMainScroll, isScrolledDown, resetMainScroll] = useOnMainScroll() 34 35 const scrollElRef = React.useRef<FlatList>(null) 35 36 const {screen} = useAnalytics() 36 37 const pal = usePalette('default') ··· 60 61 // = 61 62 useFocusEffect( 62 63 React.useCallback(() => { 63 - store.shell.setMinimalShellMode(false) 64 + setMinimalShellMode(false) 64 65 logger.debug('NotificationsScreen: Updating feed') 65 66 const softResetSub = store.onScreenSoftReset(onPressLoadLatest) 66 67 store.me.notifications.update() ··· 70 71 softResetSub.remove() 71 72 store.me.notifications.markAllRead() 72 73 } 73 - }, [store, screen, onPressLoadLatest]), 74 + }, [store, screen, onPressLoadLatest, setMinimalShellMode]), 74 75 ) 75 76 76 77 useTabFocusEffect(
+4 -4
src/view/screens/PostLikedBy.tsx
··· 5 5 import {withAuthRequired} from 'view/com/auth/withAuthRequired' 6 6 import {ViewHeader} from '../com/util/ViewHeader' 7 7 import {PostLikedBy as PostLikedByComponent} from '../com/post-thread/PostLikedBy' 8 - import {useStores} from 'state/index' 9 8 import {makeRecordUri} from 'lib/strings/url-helpers' 9 + import {useSetMinimalShellMode} from '#/state/shell' 10 10 11 11 type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostLikedBy'> 12 12 export const PostLikedByScreen = withAuthRequired(({route}: Props) => { 13 - const store = useStores() 13 + const setMinimalShellMode = useSetMinimalShellMode() 14 14 const {name, rkey} = route.params 15 15 const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) 16 16 17 17 useFocusEffect( 18 18 React.useCallback(() => { 19 - store.shell.setMinimalShellMode(false) 20 - }, [store]), 19 + setMinimalShellMode(false) 20 + }, [setMinimalShellMode]), 21 21 ) 22 22 23 23 return (
+4 -4
src/view/screens/PostRepostedBy.tsx
··· 5 5 import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' 6 6 import {ViewHeader} from '../com/util/ViewHeader' 7 7 import {PostRepostedBy as PostRepostedByComponent} from '../com/post-thread/PostRepostedBy' 8 - import {useStores} from 'state/index' 9 8 import {makeRecordUri} from 'lib/strings/url-helpers' 9 + import {useSetMinimalShellMode} from '#/state/shell' 10 10 11 11 type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostRepostedBy'> 12 12 export const PostRepostedByScreen = withAuthRequired(({route}: Props) => { 13 - const store = useStores() 14 13 const {name, rkey} = route.params 15 14 const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) 15 + const setMinimalShellMode = useSetMinimalShellMode() 16 16 17 17 useFocusEffect( 18 18 React.useCallback(() => { 19 - store.shell.setMinimalShellMode(false) 20 - }, [store]), 19 + setMinimalShellMode(false) 20 + }, [setMinimalShellMode]), 21 21 ) 22 22 23 23 return (
+6 -3
src/view/screens/PostThread.tsx
··· 15 15 import {clamp} from 'lodash' 16 16 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 17 17 import {logger} from '#/logger' 18 + import {useMinimalShellMode, useSetMinimalShellMode} from '#/state/shell' 18 19 19 20 const SHELL_FOOTER_HEIGHT = 44 20 21 ··· 22 23 export const PostThreadScreen = withAuthRequired( 23 24 observer(function PostThreadScreenImpl({route}: Props) { 24 25 const store = useStores() 26 + const minimalShellMode = useMinimalShellMode() 27 + const setMinimalShellMode = useSetMinimalShellMode() 25 28 const safeAreaInsets = useSafeAreaInsets() 26 29 const {name, rkey} = route.params 27 30 const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) ··· 33 36 34 37 useFocusEffect( 35 38 React.useCallback(() => { 36 - store.shell.setMinimalShellMode(false) 39 + setMinimalShellMode(false) 37 40 const threadCleanup = view.registerListeners() 38 41 39 42 InteractionManager.runAfterInteractions(() => { ··· 47 50 return () => { 48 51 threadCleanup() 49 52 } 50 - }, [store, view]), 53 + }, [view, setMinimalShellMode]), 51 54 ) 52 55 53 56 const onPressReply = React.useCallback(() => { ··· 80 83 treeView={!!store.preferences.thread.lab_treeViewEnabled} 81 84 /> 82 85 </View> 83 - {isMobile && !store.shell.minimalShellMode && ( 86 + {isMobile && !minimalShellMode && ( 84 87 <View 85 88 style={[ 86 89 styles.prompt,
+4 -4
src/view/screens/PrivacyPolicy.tsx
··· 5 5 import {TextLink} from 'view/com/util/Link' 6 6 import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' 7 7 import {ViewHeader} from '../com/util/ViewHeader' 8 - import {useStores} from 'state/index' 9 8 import {ScrollView} from 'view/com/util/Views' 10 9 import {usePalette} from 'lib/hooks/usePalette' 11 10 import {s} from 'lib/styles' 11 + import {useSetMinimalShellMode} from '#/state/shell' 12 12 13 13 type Props = NativeStackScreenProps<CommonNavigatorParams, 'PrivacyPolicy'> 14 14 export const PrivacyPolicyScreen = (_props: Props) => { 15 15 const pal = usePalette('default') 16 - const store = useStores() 16 + const setMinimalShellMode = useSetMinimalShellMode() 17 17 18 18 useFocusEffect( 19 19 React.useCallback(() => { 20 - store.shell.setMinimalShellMode(false) 21 - }, [store]), 20 + setMinimalShellMode(false) 21 + }, [setMinimalShellMode]), 22 22 ) 23 23 24 24 return (
+4 -2
src/view/screens/Profile.tsx
··· 30 30 import {useSetTitle} from 'lib/hooks/useSetTitle' 31 31 import {combinedDisplayName} from 'lib/strings/display-names' 32 32 import {logger} from '#/logger' 33 + import {useSetMinimalShellMode} from '#/state/shell' 33 34 34 35 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Profile'> 35 36 export const ProfileScreen = withAuthRequired( 36 37 observer(function ProfileScreenImpl({route}: Props) { 37 38 const store = useStores() 39 + const setMinimalShellMode = useSetMinimalShellMode() 38 40 const {screen, track} = useAnalytics() 39 41 const viewSelectorRef = React.useRef<ViewSelectorHandle>(null) 40 42 const name = route.params.name === 'me' ? store.me.did : route.params.name ··· 69 71 React.useCallback(() => { 70 72 const softResetSub = store.onScreenSoftReset(onSoftReset) 71 73 let aborted = false 72 - store.shell.setMinimalShellMode(false) 74 + setMinimalShellMode(false) 73 75 const feedCleanup = uiState.feed.registerListeners() 74 76 if (!hasSetup) { 75 77 uiState.setup().then(() => { ··· 84 86 feedCleanup() 85 87 softResetSub.remove() 86 88 } 87 - }, [store, onSoftReset, uiState, hasSetup]), 89 + }, [store, onSoftReset, uiState, hasSetup, setMinimalShellMode]), 88 90 ) 89 91 90 92 // events
+4 -4
src/view/screens/ProfileFeedLikedBy.tsx
··· 5 5 import {withAuthRequired} from 'view/com/auth/withAuthRequired' 6 6 import {ViewHeader} from '../com/util/ViewHeader' 7 7 import {PostLikedBy as PostLikedByComponent} from '../com/post-thread/PostLikedBy' 8 - import {useStores} from 'state/index' 9 8 import {makeRecordUri} from 'lib/strings/url-helpers' 9 + import {useSetMinimalShellMode} from '#/state/shell' 10 10 11 11 type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFeedLikedBy'> 12 12 export const ProfileFeedLikedByScreen = withAuthRequired(({route}: Props) => { 13 - const store = useStores() 13 + const setMinimalShellMode = useSetMinimalShellMode() 14 14 const {name, rkey} = route.params 15 15 const uri = makeRecordUri(name, 'app.bsky.feed.generator', rkey) 16 16 17 17 useFocusEffect( 18 18 React.useCallback(() => { 19 - store.shell.setMinimalShellMode(false) 20 - }, [store]), 19 + setMinimalShellMode(false) 20 + }, [setMinimalShellMode]), 21 21 ) 22 22 23 23 return (
+4 -4
src/view/screens/ProfileFollowers.tsx
··· 5 5 import {withAuthRequired} from 'view/com/auth/withAuthRequired' 6 6 import {ViewHeader} from '../com/util/ViewHeader' 7 7 import {ProfileFollowers as ProfileFollowersComponent} from '../com/profile/ProfileFollowers' 8 - import {useStores} from 'state/index' 8 + import {useSetMinimalShellMode} from '#/state/shell' 9 9 10 10 type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollowers'> 11 11 export const ProfileFollowersScreen = withAuthRequired(({route}: Props) => { 12 - const store = useStores() 13 12 const {name} = route.params 13 + const setMinimalShellMode = useSetMinimalShellMode() 14 14 15 15 useFocusEffect( 16 16 React.useCallback(() => { 17 - store.shell.setMinimalShellMode(false) 18 - }, [store]), 17 + setMinimalShellMode(false) 18 + }, [setMinimalShellMode]), 19 19 ) 20 20 21 21 return (
+4 -4
src/view/screens/ProfileFollows.tsx
··· 5 5 import {withAuthRequired} from 'view/com/auth/withAuthRequired' 6 6 import {ViewHeader} from '../com/util/ViewHeader' 7 7 import {ProfileFollows as ProfileFollowsComponent} from '../com/profile/ProfileFollows' 8 - import {useStores} from 'state/index' 8 + import {useSetMinimalShellMode} from '#/state/shell' 9 9 10 10 type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollows'> 11 11 export const ProfileFollowsScreen = withAuthRequired(({route}: Props) => { 12 - const store = useStores() 13 12 const {name} = route.params 13 + const setMinimalShellMode = useSetMinimalShellMode() 14 14 15 15 useFocusEffect( 16 16 React.useCallback(() => { 17 - store.shell.setMinimalShellMode(false) 18 - }, [store]), 17 + setMinimalShellMode(false) 18 + }, [setMinimalShellMode]), 19 19 ) 20 20 21 21 return (
+4 -2
src/view/screens/ProfileList.tsx
··· 45 45 import {ComposeIcon2} from 'lib/icons' 46 46 import {ListItems} from 'view/com/lists/ListItems' 47 47 import {logger} from '#/logger' 48 + import {useSetMinimalShellMode} from '#/state/shell' 48 49 49 50 const SECTION_TITLES_CURATE = ['Posts', 'About'] 50 51 const SECTION_TITLES_MOD = ['About'] ··· 105 106 listOwnerDid, 106 107 }: Props & {listOwnerDid: string}) { 107 108 const store = useStores() 109 + const setMinimalShellMode = useSetMinimalShellMode() 108 110 const {rkey} = route.params 109 111 const feedSectionRef = React.useRef<SectionRef>(null) 110 112 const aboutSectionRef = React.useRef<SectionRef>(null) ··· 124 126 125 127 useFocusEffect( 126 128 useCallback(() => { 127 - store.shell.setMinimalShellMode(false) 129 + setMinimalShellMode(false) 128 130 list.loadMore(true).then(() => { 129 131 if (list.isCuratelist) { 130 132 feed.setup() 131 133 } 132 134 }) 133 - }, [store, list, feed]), 135 + }, [setMinimalShellMode, list, feed]), 134 136 ) 135 137 136 138 const onPressAddUser = useCallback(() => {
+4 -2
src/view/screens/SavedFeeds.tsx
··· 27 27 import {Haptics} from 'lib/haptics' 28 28 import {TextLink} from 'view/com/util/Link' 29 29 import {logger} from '#/logger' 30 + import {useSetMinimalShellMode} from '#/state/shell' 30 31 31 32 const HITSLOP_TOP = { 32 33 top: 20, ··· 48 49 const store = useStores() 49 50 const {isMobile, isTabletOrDesktop} = useWebMediaQueries() 50 51 const {screen} = useAnalytics() 52 + const setMinimalShellMode = useSetMinimalShellMode() 51 53 52 54 const savedFeeds = useMemo(() => { 53 55 const model = new SavedFeedsModel(store) ··· 57 59 useFocusEffect( 58 60 useCallback(() => { 59 61 screen('SavedFeeds') 60 - store.shell.setMinimalShellMode(false) 62 + setMinimalShellMode(false) 61 63 savedFeeds.refresh() 62 - }, [screen, store, savedFeeds]), 64 + }, [screen, setMinimalShellMode, savedFeeds]), 63 65 ) 64 66 65 67 return (
+17 -7
src/view/screens/SearchMobile.tsx
··· 27 27 import {usePalette} from 'lib/hooks/usePalette' 28 28 import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' 29 29 import {isAndroid, isIOS} from 'platform/detection' 30 + import {useSetMinimalShellMode, useSetDrawerSwipeDisabled} from '#/state/shell' 30 31 31 32 type Props = NativeStackScreenProps<SearchTabNavigatorParams, 'Search'> 32 33 export const SearchScreen = withAuthRequired( 33 34 observer<Props>(function SearchScreenImpl({}: Props) { 34 35 const pal = usePalette('default') 35 36 const store = useStores() 37 + const setMinimalShellMode = useSetMinimalShellMode() 38 + const setIsDrawerSwipeDisabled = useSetDrawerSwipeDisabled() 36 39 const scrollViewRef = React.useRef<ScrollView>(null) 37 40 const flatListRef = React.useRef<FlatList>(null) 38 - const [onMainScroll] = useOnMainScroll(store) 41 + const [onMainScroll] = useOnMainScroll() 39 42 const [isInputFocused, setIsInputFocused] = React.useState<boolean>(false) 40 43 const [query, setQuery] = React.useState<string>('') 41 44 const autocompleteView = React.useMemo<UserAutocompleteModel>( ··· 75 78 setQuery('') 76 79 autocompleteView.setActive(false) 77 80 setSearchUIModel(undefined) 78 - store.shell.setIsDrawerSwipeDisabled(false) 79 - }, [setQuery, autocompleteView, store]) 81 + setIsDrawerSwipeDisabled(false) 82 + }, [setQuery, autocompleteView, setIsDrawerSwipeDisabled]) 80 83 81 84 const onSubmitQuery = React.useCallback(() => { 82 85 if (query.length === 0) { ··· 86 89 const model = new SearchUIModel(store) 87 90 model.fetch(query) 88 91 setSearchUIModel(model) 89 - store.shell.setIsDrawerSwipeDisabled(true) 90 - }, [query, setSearchUIModel, store]) 92 + setIsDrawerSwipeDisabled(true) 93 + }, [query, setSearchUIModel, store, setIsDrawerSwipeDisabled]) 91 94 92 95 const onSoftReset = React.useCallback(() => { 93 96 scrollViewRef.current?.scrollTo({x: 0, y: 0}) ··· 102 105 softResetSub.remove() 103 106 } 104 107 105 - store.shell.setMinimalShellMode(false) 108 + setMinimalShellMode(false) 106 109 autocompleteView.setup() 107 110 if (!foafs.hasData) { 108 111 foafs.fetch() ··· 112 115 } 113 116 114 117 return cleanup 115 - }, [store, autocompleteView, foafs, suggestedActors, onSoftReset]), 118 + }, [ 119 + store, 120 + autocompleteView, 121 + foafs, 122 + suggestedActors, 123 + onSoftReset, 124 + setMinimalShellMode, 125 + ]), 116 126 ) 117 127 118 128 const onPress = useCallback(() => {
+4 -2
src/view/screens/Settings.tsx
··· 46 46 import {makeProfileLink} from 'lib/routes/links' 47 47 import {AccountDropdownBtn} from 'view/com/util/AccountDropdownBtn' 48 48 import {logger} from '#/logger' 49 + import {useSetMinimalShellMode} from '#/state/shell' 49 50 50 51 // TEMPORARY (APP-700) 51 52 // remove after backend testing finishes ··· 58 59 observer(function Settings({}: Props) { 59 60 const pal = usePalette('default') 60 61 const store = useStores() 62 + const setMinimalShellMode = useSetMinimalShellMode() 61 63 const navigation = useNavigation<NavigationProp>() 62 64 const {isMobile} = useWebMediaQueries() 63 65 const {screen, track} = useAnalytics() ··· 88 90 useFocusEffect( 89 91 React.useCallback(() => { 90 92 screen('Settings') 91 - store.shell.setMinimalShellMode(false) 92 - }, [screen, store]), 93 + setMinimalShellMode(false) 94 + }, [screen, setMinimalShellMode]), 93 95 ) 94 96 95 97 const onPressAddAccount = React.useCallback(() => {
+4 -4
src/view/screens/Support.tsx
··· 3 3 import {useFocusEffect} from '@react-navigation/native' 4 4 import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' 5 5 import {ViewHeader} from '../com/util/ViewHeader' 6 - import {useStores} from 'state/index' 7 6 import {Text} from 'view/com/util/text/Text' 8 7 import {TextLink} from 'view/com/util/Link' 9 8 import {CenteredView} from 'view/com/util/Views' 10 9 import {usePalette} from 'lib/hooks/usePalette' 11 10 import {s} from 'lib/styles' 12 11 import {HELP_DESK_URL} from 'lib/constants' 12 + import {useSetMinimalShellMode} from '#/state/shell' 13 13 14 14 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Support'> 15 15 export const SupportScreen = (_props: Props) => { 16 - const store = useStores() 17 16 const pal = usePalette('default') 17 + const setMinimalShellMode = useSetMinimalShellMode() 18 18 19 19 useFocusEffect( 20 20 React.useCallback(() => { 21 - store.shell.setMinimalShellMode(false) 22 - }, [store]), 21 + setMinimalShellMode(false) 22 + }, [setMinimalShellMode]), 23 23 ) 24 24 25 25 return (
+4 -4
src/view/screens/TermsOfService.tsx
··· 5 5 import {TextLink} from 'view/com/util/Link' 6 6 import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' 7 7 import {ViewHeader} from '../com/util/ViewHeader' 8 - import {useStores} from 'state/index' 9 8 import {ScrollView} from 'view/com/util/Views' 10 9 import {usePalette} from 'lib/hooks/usePalette' 11 10 import {s} from 'lib/styles' 11 + import {useSetMinimalShellMode} from '#/state/shell' 12 12 13 13 type Props = NativeStackScreenProps<CommonNavigatorParams, 'TermsOfService'> 14 14 export const TermsOfServiceScreen = (_props: Props) => { 15 15 const pal = usePalette('default') 16 - const store = useStores() 16 + const setMinimalShellMode = useSetMinimalShellMode() 17 17 18 18 useFocusEffect( 19 19 React.useCallback(() => { 20 - store.shell.setMinimalShellMode(false) 21 - }, [store]), 20 + setMinimalShellMode(false) 21 + }, [setMinimalShellMode]), 22 22 ) 23 23 24 24 return (
+13 -10
src/view/shell/Drawer.tsx
··· 43 43 import {useNavigationTabState} from 'lib/hooks/useNavigationTabState' 44 44 import {isWeb} from 'platform/detection' 45 45 import {formatCount, formatCountShortOnly} from 'view/com/util/numeric/format' 46 + import {useSetDrawerOpen} from '#/state/shell' 46 47 47 48 export const DrawerContent = observer(function DrawerContentImpl() { 48 49 const theme = useTheme() 49 50 const pal = usePalette('default') 50 51 const store = useStores() 52 + const setDrawerOpen = useSetDrawerOpen() 51 53 const navigation = useNavigation<NavigationProp>() 52 54 const {track} = useAnalytics() 53 55 const {isAtHome, isAtSearch, isAtFeeds, isAtNotifications, isAtMyProfile} = ··· 62 64 (tab: string) => { 63 65 track('Menu:ItemClicked', {url: tab}) 64 66 const state = navigation.getState() 65 - store.shell.closeDrawer() 67 + setDrawerOpen(false) 66 68 if (isWeb) { 67 69 // hack because we have flat navigator for web and MyProfile does not exist on the web navigator -ansh 68 70 if (tab === 'MyProfile') { ··· 83 85 } 84 86 } 85 87 }, 86 - [store, track, navigation], 88 + [store, track, navigation, setDrawerOpen], 87 89 ) 88 90 89 91 const onPressHome = React.useCallback(() => onPressTab('Home'), [onPressTab]) ··· 110 112 const onPressLists = React.useCallback(() => { 111 113 track('Menu:ItemClicked', {url: 'Lists'}) 112 114 navigation.navigate('Lists') 113 - store.shell.closeDrawer() 114 - }, [navigation, track, store.shell]) 115 + setDrawerOpen(false) 116 + }, [navigation, track, setDrawerOpen]) 115 117 116 118 const onPressModeration = React.useCallback(() => { 117 119 track('Menu:ItemClicked', {url: 'Moderation'}) 118 120 navigation.navigate('Moderation') 119 - store.shell.closeDrawer() 120 - }, [navigation, track, store.shell]) 121 + setDrawerOpen(false) 122 + }, [navigation, track, setDrawerOpen]) 121 123 122 124 const onPressSettings = React.useCallback(() => { 123 125 track('Menu:ItemClicked', {url: 'Settings'}) 124 126 navigation.navigate('Settings') 125 - store.shell.closeDrawer() 126 - }, [navigation, track, store.shell]) 127 + setDrawerOpen(false) 128 + }, [navigation, track, setDrawerOpen]) 127 129 128 130 const onPressFeedback = React.useCallback(() => { 129 131 track('Menu:FeedbackClicked') ··· 437 439 }) { 438 440 const {track} = useAnalytics() 439 441 const store = useStores() 442 + const setDrawerOpen = useSetDrawerOpen() 440 443 const pal = usePalette('default') 441 444 const {invitesAvailable} = store.me 442 445 const onPress = React.useCallback(() => { 443 446 track('Menu:ItemClicked', {url: '#invite-codes'}) 444 - store.shell.closeDrawer() 447 + setDrawerOpen(false) 445 448 store.shell.openModal({name: 'invite-codes'}) 446 - }, [store, track]) 449 + }, [store, track, setDrawerOpen]) 447 450 return ( 448 451 <TouchableOpacity 449 452 testID="menuItemInviteCodes"
+2 -2
src/view/shell/bottom-bar/BottomBar.tsx
··· 37 37 const {isAtHome, isAtSearch, isAtFeeds, isAtNotifications, isAtMyProfile} = 38 38 useNavigationTabState() 39 39 40 - const {footerMinimalShellTransform} = useMinimalShellMode() 40 + const {minimalShellMode, footerMinimalShellTransform} = useMinimalShellMode() 41 41 const {notifications} = store.me 42 42 43 43 const onPressTab = React.useCallback( ··· 83 83 pal.border, 84 84 {paddingBottom: clamp(safeAreaInsets.bottom, 15, 30)}, 85 85 footerMinimalShellTransform, 86 - store.shell.minimalShellMode && styles.disabled, 86 + minimalShellMode && styles.disabled, 87 87 ]}> 88 88 <Btn 89 89 testID="bottomBarHomeBtn"
+25 -12
src/view/shell/index.tsx
··· 6 6 StyleSheet, 7 7 useWindowDimensions, 8 8 View, 9 + BackHandler, 9 10 } from 'react-native' 10 11 import {useSafeAreaInsets} from 'react-native-safe-area-context' 11 12 import {Drawer} from 'react-native-drawer-layout' ··· 18 19 import {Composer} from './Composer' 19 20 import {useTheme} from 'lib/ThemeContext' 20 21 import {usePalette} from 'lib/hooks/usePalette' 21 - import * as backHandler from 'lib/routes/back-handler' 22 22 import {RoutesContainer, TabsNavigator} from '../../Navigation' 23 23 import {isStateAtTabRoot} from 'lib/routes/helpers' 24 24 import { ··· 26 26 initialWindowMetrics, 27 27 } from 'react-native-safe-area-context' 28 28 import {useOTAUpdate} from 'lib/hooks/useOTAUpdate' 29 + import { 30 + useIsDrawerOpen, 31 + useSetDrawerOpen, 32 + useIsDrawerSwipeDisabled, 33 + } from '#/state/shell' 34 + import {isAndroid} from 'platform/detection' 29 35 30 36 const ShellInner = observer(function ShellInnerImpl() { 31 37 const store = useStores() 38 + const isDrawerOpen = useIsDrawerOpen() 39 + const isDrawerSwipeDisabled = useIsDrawerSwipeDisabled() 40 + const setIsDrawerOpen = useSetDrawerOpen() 32 41 useOTAUpdate() // this hook polls for OTA updates every few seconds 33 42 const winDim = useWindowDimensions() 34 43 const safeAreaInsets = useSafeAreaInsets() ··· 38 47 ) 39 48 const renderDrawerContent = React.useCallback(() => <DrawerContent />, []) 40 49 const onOpenDrawer = React.useCallback( 41 - () => store.shell.openDrawer(), 42 - [store], 50 + () => setIsDrawerOpen(true), 51 + [setIsDrawerOpen], 43 52 ) 44 53 const onCloseDrawer = React.useCallback( 45 - () => store.shell.closeDrawer(), 46 - [store], 54 + () => setIsDrawerOpen(false), 55 + [setIsDrawerOpen], 47 56 ) 48 57 const canGoBack = useNavigationState(state => !isStateAtTabRoot(state)) 49 58 React.useEffect(() => { 50 - const listener = backHandler.init(store) 59 + let listener = {remove() {}} 60 + if (isAndroid) { 61 + listener = BackHandler.addEventListener('hardwareBackPress', () => { 62 + setIsDrawerOpen(false) 63 + return store.shell.closeAnyActiveElement() 64 + }) 65 + } 51 66 return () => { 52 - listener() 67 + listener.remove() 53 68 } 54 - }, [store]) 69 + }, [store, setIsDrawerOpen]) 55 70 56 71 return ( 57 72 <> ··· 59 74 <ErrorBoundary> 60 75 <Drawer 61 76 renderDrawerContent={renderDrawerContent} 62 - open={store.shell.isDrawerOpen} 77 + open={isDrawerOpen} 63 78 onOpen={onOpenDrawer} 64 79 onClose={onCloseDrawer} 65 80 swipeEdgeWidth={winDim.width / 2} 66 81 swipeEnabled={ 67 - !canGoBack && 68 - store.session.hasSession && 69 - !store.shell.isDrawerSwipeDisabled 82 + !canGoBack && store.session.hasSession && !isDrawerSwipeDisabled 70 83 }> 71 84 <TabsNavigator /> 72 85 </Drawer>
+7 -3
src/view/shell/index.web.tsx
··· 17 17 import {useNavigation} from '@react-navigation/native' 18 18 import {NavigationProp} from 'lib/routes/types' 19 19 import {useAuxClick} from 'lib/hooks/useAuxClick' 20 + import {useIsDrawerOpen, useSetDrawerOpen} from '#/state/shell' 20 21 21 22 const ShellInner = observer(function ShellInnerImpl() { 22 23 const store = useStores() 24 + const isDrawerOpen = useIsDrawerOpen() 25 + const setDrawerOpen = useSetDrawerOpen() 23 26 const {isDesktop, isMobile} = useWebMediaQueries() 24 27 const navigator = useNavigation<NavigationProp>() 25 28 useAuxClick() 26 29 27 30 useEffect(() => { 28 31 navigator.addListener('state', () => { 32 + setDrawerOpen(false) 29 33 store.shell.closeAnyActiveElement() 30 34 }) 31 - }, [navigator, store.shell]) 35 + }, [navigator, store.shell, setDrawerOpen]) 32 36 33 37 const showBottomBar = isMobile && !store.onboarding.isActive 34 38 const showSideNavs = ··· 57 61 {showBottomBar && <BottomBarWeb />} 58 62 <ModalsContainer /> 59 63 <Lightbox /> 60 - {!isDesktop && store.shell.isDrawerOpen && ( 64 + {!isDesktop && isDrawerOpen && ( 61 65 <TouchableOpacity 62 - onPress={() => store.shell.closeDrawer()} 66 + onPress={() => setDrawerOpen(false)} 63 67 style={styles.drawerMask} 64 68 accessibilityLabel="Close navigation footer" 65 69 accessibilityHint="Closes bottom navigation bar">