diff options
Diffstat (limited to 'src/view/shell/mobile')
-rw-r--r-- | src/view/shell/mobile/BottomBar.tsx | 271 | ||||
-rw-r--r-- | src/view/shell/mobile/Composer.tsx | 86 | ||||
-rw-r--r-- | src/view/shell/mobile/Menu.tsx | 354 | ||||
-rw-r--r-- | src/view/shell/mobile/index.tsx | 335 |
4 files changed, 0 insertions, 1046 deletions
diff --git a/src/view/shell/mobile/BottomBar.tsx b/src/view/shell/mobile/BottomBar.tsx deleted file mode 100644 index 73c2501ab..000000000 --- a/src/view/shell/mobile/BottomBar.tsx +++ /dev/null @@ -1,271 +0,0 @@ -import React from 'react' -import { - Animated, - GestureResponderEvent, - StyleSheet, - TouchableOpacity, - View, -} from 'react-native' -import {useSafeAreaInsets} from 'react-native-safe-area-context' -import {observer} from 'mobx-react-lite' -import {Text} from 'view/com/util/text/Text' -import {useStores} from 'state/index' -import {useAnalytics} from 'lib/analytics' -import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' -import {TabPurpose, TabPurposeMainPath} from 'state/models/navigation' -import {clamp} from 'lib/numbers' -import { - HomeIcon, - HomeIconSolid, - MagnifyingGlassIcon2, - MagnifyingGlassIcon2Solid, - BellIcon, - BellIconSolid, - UserIcon, -} from 'lib/icons' -import {colors} from 'lib/styles' -import {usePalette} from 'lib/hooks/usePalette' - -export const BottomBar = observer(() => { - const store = useStores() - const pal = usePalette('default') - const minimalShellInterp = useAnimatedValue(0) - const safeAreaInsets = useSafeAreaInsets() - const {track} = useAnalytics() - - React.useEffect(() => { - if (store.shell.minimalShellMode) { - Animated.timing(minimalShellInterp, { - toValue: 1, - duration: 100, - useNativeDriver: true, - isInteraction: false, - }).start() - } else { - Animated.timing(minimalShellInterp, { - toValue: 0, - duration: 100, - useNativeDriver: true, - isInteraction: false, - }).start() - } - }, [minimalShellInterp, store.shell.minimalShellMode]) - const footerMinimalShellTransform = { - transform: [{translateY: Animated.multiply(minimalShellInterp, 100)}], - } - - const onPressHome = React.useCallback(() => { - track('MobileShell:HomeButtonPressed') - if (store.nav.tab.fixedTabPurpose === TabPurpose.Default) { - if (!store.nav.tab.canGoBack) { - store.emitScreenSoftReset() - } else { - store.nav.tab.fixedTabReset() - } - } else { - store.nav.switchTo(TabPurpose.Default, false) - if (store.nav.tab.index === 0) { - store.nav.tab.fixedTabReset() - } - } - }, [store, track]) - const onPressSearch = React.useCallback(() => { - track('MobileShell:SearchButtonPressed') - if (store.nav.tab.fixedTabPurpose === TabPurpose.Search) { - if (!store.nav.tab.canGoBack) { - store.emitScreenSoftReset() - } else { - store.nav.tab.fixedTabReset() - } - } else { - store.nav.switchTo(TabPurpose.Search, false) - if (store.nav.tab.index === 0) { - store.nav.tab.fixedTabReset() - } - } - }, [store, track]) - const onPressNotifications = React.useCallback(() => { - track('MobileShell:NotificationsButtonPressed') - if (store.nav.tab.fixedTabPurpose === TabPurpose.Notifs) { - if (!store.nav.tab.canGoBack) { - store.emitScreenSoftReset() - } else { - store.nav.tab.fixedTabReset() - } - } else { - store.nav.switchTo(TabPurpose.Notifs, false) - if (store.nav.tab.index === 0) { - store.nav.tab.fixedTabReset() - } - } - }, [store, track]) - const onPressProfile = React.useCallback(() => { - track('MobileShell:ProfileButtonPressed') - store.nav.navigate(`/profile/${store.me.handle}`) - }, [store, track]) - - const isAtHome = - store.nav.tab.current.url === TabPurposeMainPath[TabPurpose.Default] - const isAtSearch = - store.nav.tab.current.url === TabPurposeMainPath[TabPurpose.Search] - const isAtNotifications = - store.nav.tab.current.url === TabPurposeMainPath[TabPurpose.Notifs] - - return ( - <Animated.View - style={[ - styles.bottomBar, - pal.view, - pal.border, - {paddingBottom: clamp(safeAreaInsets.bottom, 15, 30)}, - footerMinimalShellTransform, - ]}> - <Btn - icon={ - isAtHome ? ( - <HomeIconSolid - strokeWidth={4} - size={24} - style={[styles.ctrlIcon, pal.text, styles.homeIcon]} - /> - ) : ( - <HomeIcon - strokeWidth={4} - size={24} - style={[styles.ctrlIcon, pal.text, styles.homeIcon]} - /> - ) - } - onPress={onPressHome} - /> - <Btn - icon={ - isAtSearch ? ( - <MagnifyingGlassIcon2Solid - size={25} - style={[styles.ctrlIcon, pal.text, styles.searchIcon]} - strokeWidth={1.8} - /> - ) : ( - <MagnifyingGlassIcon2 - size={25} - style={[styles.ctrlIcon, pal.text, styles.searchIcon]} - strokeWidth={1.8} - /> - ) - } - onPress={onPressSearch} - /> - <Btn - icon={ - isAtNotifications ? ( - <BellIconSolid - size={24} - strokeWidth={1.9} - style={[styles.ctrlIcon, pal.text, styles.bellIcon]} - /> - ) : ( - <BellIcon - size={24} - strokeWidth={1.9} - style={[styles.ctrlIcon, pal.text, styles.bellIcon]} - /> - ) - } - onPress={onPressNotifications} - notificationCount={store.me.notifications.unreadCount} - /> - <Btn - icon={ - <View style={styles.ctrlIconSizingWrapper}> - <UserIcon - size={28} - strokeWidth={1.5} - style={[styles.ctrlIcon, pal.text, styles.profileIcon]} - /> - </View> - } - onPress={onPressProfile} - /> - </Animated.View> - ) -}) - -function Btn({ - icon, - notificationCount, - onPress, - onLongPress, -}: { - icon: JSX.Element - notificationCount?: number - onPress?: (event: GestureResponderEvent) => void - onLongPress?: (event: GestureResponderEvent) => void -}) { - return ( - <TouchableOpacity - style={styles.ctrl} - onPress={onLongPress ? onPress : undefined} - onPressIn={onLongPress ? undefined : onPress} - onLongPress={onLongPress}> - {notificationCount ? ( - <View style={styles.notificationCount}> - <Text style={styles.notificationCountLabel}>{notificationCount}</Text> - </View> - ) : undefined} - {icon} - </TouchableOpacity> - ) -} - -const styles = StyleSheet.create({ - bottomBar: { - position: 'absolute', - bottom: 0, - left: 0, - right: 0, - flexDirection: 'row', - borderTopWidth: 1, - paddingLeft: 5, - paddingRight: 10, - }, - ctrl: { - flex: 1, - paddingTop: 13, - paddingBottom: 4, - }, - notificationCount: { - position: 'absolute', - left: '52%', - top: 10, - backgroundColor: colors.blue3, - paddingHorizontal: 4, - paddingBottom: 1, - borderRadius: 8, - zIndex: 1, - }, - notificationCountLabel: { - fontSize: 12, - fontWeight: 'bold', - color: colors.white, - }, - ctrlIcon: { - marginLeft: 'auto', - marginRight: 'auto', - }, - ctrlIconSizingWrapper: { - height: 27, - }, - homeIcon: { - top: 0, - }, - searchIcon: { - top: -2, - }, - bellIcon: { - top: -2.5, - }, - profileIcon: { - top: -4, - }, -}) diff --git a/src/view/shell/mobile/Composer.tsx b/src/view/shell/mobile/Composer.tsx deleted file mode 100644 index 5fca118bd..000000000 --- a/src/view/shell/mobile/Composer.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React, {useEffect} from 'react' -import {observer} from 'mobx-react-lite' -import {Animated, Easing, Platform, StyleSheet, View} from 'react-native' -import {ComposePost} from '../../com/composer/ComposePost' -import {ComposerOpts} from 'state/models/shell-ui' -import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' -import {usePalette} from 'lib/hooks/usePalette' - -export const Composer = observer( - ({ - active, - winHeight, - replyTo, - imagesOpen, - onPost, - onClose, - quote, - }: { - active: boolean - winHeight: number - replyTo?: ComposerOpts['replyTo'] - imagesOpen?: ComposerOpts['imagesOpen'] - onPost?: ComposerOpts['onPost'] - onClose: () => void - quote?: ComposerOpts['quote'] - }) => { - const pal = usePalette('default') - const initInterp = useAnimatedValue(0) - - useEffect(() => { - if (active) { - Animated.timing(initInterp, { - toValue: 1, - duration: 300, - easing: Easing.out(Easing.exp), - useNativeDriver: true, - }).start() - } else { - initInterp.setValue(0) - } - }, [initInterp, active]) - const wrapperAnimStyle = { - transform: [ - { - translateY: initInterp.interpolate({ - inputRange: [0, 1], - outputRange: [winHeight, 0], - }), - }, - ], - } - - // rendering - // = - - if (!active) { - return <View /> - } - - return ( - <Animated.View style={[styles.wrapper, pal.view, wrapperAnimStyle]}> - <ComposePost - replyTo={replyTo} - imagesOpen={imagesOpen} - onPost={onPost} - onClose={onClose} - quote={quote} - /> - </Animated.View> - ) - }, -) - -const styles = StyleSheet.create({ - wrapper: { - position: 'absolute', - top: 0, - bottom: 0, - width: '100%', - ...Platform.select({ - ios: { - paddingTop: 24, - }, - }), - }, -}) diff --git a/src/view/shell/mobile/Menu.tsx b/src/view/shell/mobile/Menu.tsx deleted file mode 100644 index 927e712e1..000000000 --- a/src/view/shell/mobile/Menu.tsx +++ /dev/null @@ -1,354 +0,0 @@ -import React from 'react' -import { - Linking, - StyleProp, - StyleSheet, - TouchableOpacity, - View, - ViewStyle, -} from 'react-native' -import {observer} from 'mobx-react-lite' -import { - FontAwesomeIcon, - FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' -import {s, colors} from 'lib/styles' -import {FEEDBACK_FORM_URL} from 'lib/constants' -import {useStores} from 'state/index' -import { - HomeIcon, - HomeIconSolid, - BellIcon, - BellIconSolid, - UserIcon, - CogIcon, - MagnifyingGlassIcon2, - MagnifyingGlassIcon2Solid, - MoonIcon, -} from 'lib/icons' -import {TabPurpose, TabPurposeMainPath} from 'state/models/navigation' -import {UserAvatar} from '../../com/util/UserAvatar' -import {Text} from '../../com/util/text/Text' -import {useTheme} from 'lib/ThemeContext' -import {usePalette} from 'lib/hooks/usePalette' -import {useAnalytics} from 'lib/analytics' -import {pluralize} from 'lib/strings/helpers' - -export const Menu = observer(({onClose}: {onClose: () => void}) => { - const theme = useTheme() - const pal = usePalette('default') - const store = useStores() - const {track} = useAnalytics() - - // events - // = - - const onNavigate = (url: string) => { - track('Menu:ItemClicked', {url}) - - onClose() - if (url === TabPurposeMainPath[TabPurpose.Notifs]) { - store.nav.switchTo(TabPurpose.Notifs, true) - } else if (url === TabPurposeMainPath[TabPurpose.Search]) { - store.nav.switchTo(TabPurpose.Search, true) - } else { - store.nav.switchTo(TabPurpose.Default, true) - if (url !== '/') { - store.nav.navigate(url) - } - } - } - - const onPressFeedback = () => { - track('Menu:FeedbackClicked') - Linking.openURL(FEEDBACK_FORM_URL) - } - - // rendering - // = - - const MenuItem = ({ - icon, - label, - count, - url, - bold, - onPress, - }: { - icon: JSX.Element - label: string - count?: number - url?: string - bold?: boolean - onPress?: () => void - }) => ( - <TouchableOpacity - testID={`menuItemButton-${label}`} - style={styles.menuItem} - onPress={onPress ? onPress : () => onNavigate(url || '/')}> - <View style={[styles.menuItemIconWrapper]}> - {icon} - {count ? ( - <View style={styles.menuItemCount}> - <Text style={styles.menuItemCountLabel}>{count}</Text> - </View> - ) : undefined} - </View> - <Text - type={bold ? '2xl-bold' : '2xl'} - style={[pal.text, s.flex1]} - numberOfLines={1}> - {label} - </Text> - </TouchableOpacity> - ) - - const onDarkmodePress = () => { - track('Menu:ItemClicked', {url: '/darkmode'}) - store.shell.setDarkMode(!store.shell.darkMode) - } - - const isAtHome = - store.nav.tab.current.url === TabPurposeMainPath[TabPurpose.Default] - const isAtSearch = - store.nav.tab.current.url === TabPurposeMainPath[TabPurpose.Search] - const isAtNotifications = - store.nav.tab.current.url === TabPurposeMainPath[TabPurpose.Notifs] - - return ( - <View - testID="menuView" - style={[ - styles.view, - theme.colorScheme === 'light' ? pal.view : styles.viewDarkMode, - ]}> - <TouchableOpacity - testID="profileCardButton" - onPress={() => onNavigate(`/profile/${store.me.handle}`)}> - <UserAvatar - size={80} - displayName={store.me.displayName} - handle={store.me.handle} - avatar={store.me.avatar} - /> - <Text - type="title-lg" - style={[pal.text, s.bold, styles.profileCardDisplayName]}> - {store.me.displayName || store.me.handle} - </Text> - <Text type="2xl" style={[pal.textLight, styles.profileCardHandle]}> - @{store.me.handle} - </Text> - <Text type="xl" style={[pal.textLight, styles.profileCardFollowers]}> - <Text type="xl-medium" style={pal.text}> - {store.me.followersCount || 0} - </Text>{' '} - {pluralize(store.me.followersCount || 0, 'follower')} ·{' '} - <Text type="xl-medium" style={pal.text}> - {store.me.followsCount || 0} - </Text>{' '} - following - </Text> - </TouchableOpacity> - <View style={s.flex1} /> - <View> - <MenuItem - icon={ - isAtSearch ? ( - <MagnifyingGlassIcon2Solid - style={pal.text as StyleProp<ViewStyle>} - size={24} - strokeWidth={1.7} - /> - ) : ( - <MagnifyingGlassIcon2 - style={pal.text as StyleProp<ViewStyle>} - size={24} - strokeWidth={1.7} - /> - ) - } - label="Search" - url="/search" - bold={isAtSearch} - /> - <MenuItem - icon={ - isAtHome ? ( - <HomeIconSolid - style={pal.text as StyleProp<ViewStyle>} - size="24" - strokeWidth={3.25} - fillOpacity={1} - /> - ) : ( - <HomeIcon - style={pal.text as StyleProp<ViewStyle>} - size="24" - strokeWidth={3.25} - /> - ) - } - label="Home" - url="/" - bold={isAtHome} - /> - <MenuItem - icon={ - isAtNotifications ? ( - <BellIconSolid - style={pal.text as StyleProp<ViewStyle>} - size="24" - strokeWidth={1.7} - fillOpacity={1} - /> - ) : ( - <BellIcon - style={pal.text as StyleProp<ViewStyle>} - size="24" - strokeWidth={1.7} - /> - ) - } - label="Notifications" - url="/notifications" - count={store.me.notifications.unreadCount} - bold={isAtNotifications} - /> - <MenuItem - icon={ - <UserIcon - style={pal.text as StyleProp<ViewStyle>} - size="26" - strokeWidth={1.5} - /> - } - label="Profile" - url={`/profile/${store.me.handle}`} - /> - <MenuItem - icon={ - <CogIcon - style={pal.text as StyleProp<ViewStyle>} - size="26" - strokeWidth={1.75} - /> - } - label="Settings" - url="/settings" - /> - </View> - <View style={s.flex1} /> - <View style={styles.footer}> - <TouchableOpacity - onPress={onDarkmodePress} - style={[ - styles.footerBtn, - theme.colorScheme === 'light' ? pal.btn : styles.footerBtnDarkMode, - ]}> - <MoonIcon - size={22} - style={pal.text as StyleProp<ViewStyle>} - strokeWidth={2} - /> - </TouchableOpacity> - <TouchableOpacity - onPress={onPressFeedback} - style={[ - styles.footerBtn, - styles.footerBtnFeedback, - theme.colorScheme === 'light' - ? styles.footerBtnFeedbackLight - : styles.footerBtnFeedbackDark, - ]}> - <FontAwesomeIcon - style={pal.link as FontAwesomeIconStyle} - size={19} - icon={['far', 'message']} - /> - <Text type="2xl-medium" style={[pal.link, s.pl10]}> - Feedback - </Text> - </TouchableOpacity> - </View> - </View> - ) -}) - -const styles = StyleSheet.create({ - view: { - flex: 1, - paddingTop: 20, - paddingBottom: 50, - paddingLeft: 30, - }, - viewDarkMode: { - backgroundColor: '#1B1919', - }, - - profileCardDisplayName: { - marginTop: 20, - paddingRight: 20, - }, - profileCardHandle: { - marginTop: 4, - paddingRight: 20, - }, - profileCardFollowers: { - marginTop: 16, - paddingRight: 20, - }, - - menuItem: { - flexDirection: 'row', - alignItems: 'center', - paddingVertical: 16, - paddingRight: 10, - }, - menuItemIconWrapper: { - width: 24, - height: 24, - alignItems: 'center', - justifyContent: 'center', - marginRight: 12, - }, - menuItemCount: { - position: 'absolute', - right: -6, - top: -2, - backgroundColor: colors.red3, - paddingHorizontal: 4, - paddingBottom: 1, - borderRadius: 6, - }, - menuItemCountLabel: { - fontSize: 12, - fontWeight: 'bold', - color: colors.white, - }, - - footer: { - flexDirection: 'row', - justifyContent: 'space-between', - paddingRight: 30, - paddingTop: 80, - }, - footerBtn: { - flexDirection: 'row', - alignItems: 'center', - padding: 10, - borderRadius: 25, - }, - footerBtnDarkMode: { - backgroundColor: colors.black, - }, - footerBtnFeedback: { - paddingHorizontal: 24, - }, - footerBtnFeedbackLight: { - backgroundColor: '#DDEFFF', - }, - footerBtnFeedbackDark: { - backgroundColor: colors.blue6, - }, -}) diff --git a/src/view/shell/mobile/index.tsx b/src/view/shell/mobile/index.tsx deleted file mode 100644 index 01df6c165..000000000 --- a/src/view/shell/mobile/index.tsx +++ /dev/null @@ -1,335 +0,0 @@ -import React, {useState} from 'react' -import {observer} from 'mobx-react-lite' -import { - Animated, - StatusBar, - StyleSheet, - TouchableWithoutFeedback, - useWindowDimensions, - View, -} from 'react-native' -import {ScreenContainer, Screen} from 'react-native-screens' -import {useSafeAreaInsets} from 'react-native-safe-area-context' -import {IconProp} from '@fortawesome/fontawesome-svg-core' -import {useStores} from 'state/index' -import {NavigationModel} from 'state/models/navigation' -import {match, MatchResult} from '../../routes' -import {Login} from '../../screens/Login' -import {Menu} from './Menu' -import {BottomBar} from './BottomBar' -import {HorzSwipe} from '../../com/util/gestures/HorzSwipe' -import {ModalsContainer} from '../../com/modals/Modal' -import {Lightbox} from '../../com/lightbox/Lightbox' -import {Text} from '../../com/util/text/Text' -import {ErrorBoundary} from '../../com/util/ErrorBoundary' -import {Composer} from './Composer' -import {s, colors} from 'lib/styles' -import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' -import {useTheme} from 'lib/ThemeContext' -import {usePalette} from 'lib/hooks/usePalette' - -export const MobileShell: React.FC = observer(() => { - const theme = useTheme() - const pal = usePalette('default') - const store = useStores() - const winDim = useWindowDimensions() - const [menuSwipingDirection, setMenuSwipingDirection] = useState(0) - const swipeGestureInterp = useAnimatedValue(0) - const safeAreaInsets = useSafeAreaInsets() - const screenRenderDesc = constructScreenRenderDesc(store.nav) - - // navigation swipes - // = - const isMenuActive = store.shell.isMainMenuOpen - const canSwipeLeft = store.nav.tab.canGoBack || !isMenuActive - const canSwipeRight = isMenuActive - const onNavSwipeStartDirection = (dx: number) => { - if (dx < 0 && !store.nav.tab.canGoBack) { - setMenuSwipingDirection(dx) - } else if (dx > 0 && isMenuActive) { - setMenuSwipingDirection(dx) - } else { - setMenuSwipingDirection(0) - } - } - const onNavSwipeEnd = (dx: number) => { - if (dx < 0) { - if (store.nav.tab.canGoBack) { - store.nav.tab.goBack() - } else { - store.shell.setMainMenuOpen(true) - } - } else if (dx > 0) { - if (isMenuActive) { - store.shell.setMainMenuOpen(false) - } - } - setMenuSwipingDirection(0) - } - const swipeTranslateX = Animated.multiply( - swipeGestureInterp, - winDim.width * -1, - ) - const swipeTransform = store.nav.tab.canGoBack - ? {transform: [{translateX: swipeTranslateX}]} - : undefined - let shouldRenderMenu = false - let menuTranslateX - const menuDrawerWidth = winDim.width - 100 - if (isMenuActive) { - // menu is active, interpret swipes as closes - menuTranslateX = Animated.multiply(swipeGestureInterp, menuDrawerWidth * -1) - shouldRenderMenu = true - } else if (!store.nav.tab.canGoBack) { - // at back of history, interpret swipes as opens - menuTranslateX = Animated.subtract( - menuDrawerWidth * -1, - Animated.multiply(swipeGestureInterp, menuDrawerWidth), - ) - shouldRenderMenu = true - } - const menuSwipeTransform = menuTranslateX - ? { - transform: [{translateX: menuTranslateX}], - } - : undefined - const swipeOpacity = { - opacity: swipeGestureInterp.interpolate({ - inputRange: [-1, 0, 1], - outputRange: [0, 0.6, 0], - }), - } - const menuSwipeOpacity = - menuSwipingDirection !== 0 - ? { - opacity: swipeGestureInterp.interpolate({ - inputRange: menuSwipingDirection > 0 ? [0, 1] : [-1, 0], - outputRange: [0.6, 0], - }), - } - : undefined - - if (store.hackUpgradeNeeded) { - return ( - <View style={styles.outerContainer}> - <View style={[s.flexCol, s.p20, s.h100pct]}> - <View style={s.flex1} /> - <View> - <Text type="title-2xl" style={s.pb10}> - Update required - </Text> - <Text style={[s.pb20, s.bold]}> - Please update your app to the latest version. If no update is - available yet, please check the App Store in a day or so. - </Text> - <Text type="title" style={s.pb10}> - What's happening? - </Text> - <Text style={s.pb10}> - We're in the final stages of the AT Protocol's v1 development. To - make sure everything works as well as possible, we're making final - breaking changes to the APIs. - </Text> - <Text> - If we didn't botch this process, a new version of the app should - be available now. - </Text> - </View> - <View style={s.flex1} /> - <View style={s.footerSpacer} /> - </View> - </View> - ) - } - - if (!store.session.hasSession) { - return ( - <View style={styles.outerContainer}> - <StatusBar - barStyle={ - theme.colorScheme === 'dark' ? 'light-content' : 'dark-content' - } - /> - <Login /> - <ModalsContainer /> - </View> - ) - } - - const screenBg = { - backgroundColor: theme.colorScheme === 'dark' ? colors.black : colors.gray1, - } - return ( - <View testID="mobileShellView" style={[styles.outerContainer, pal.view]}> - <StatusBar - barStyle={ - theme.colorScheme === 'dark' ? 'light-content' : 'dark-content' - } - /> - <View style={[styles.innerContainer, {paddingTop: safeAreaInsets.top}]}> - <HorzSwipe - distThresholdDivisor={2.5} - useNativeDriver - panX={swipeGestureInterp} - swipeEnabled - canSwipeLeft={canSwipeLeft} - canSwipeRight={canSwipeRight} - onSwipeStartDirection={onNavSwipeStartDirection} - onSwipeEnd={onNavSwipeEnd}> - <ScreenContainer style={styles.screenContainer}> - {screenRenderDesc.screens.map( - ({Com, navIdx, params, key, current, previous}) => { - if (isMenuActive) { - // HACK menu is active, treat current as previous - if (previous) { - previous = false - } else if (current) { - current = false - previous = true - } - } - return ( - <Screen - key={key} - style={[StyleSheet.absoluteFill]} - activityState={current ? 2 : previous ? 1 : 0}> - <Animated.View - style={ - current ? [styles.screenMask, swipeOpacity] : undefined - } - /> - <Animated.View - style={[ - s.h100pct, - screenBg, - current ? [swipeTransform] : undefined, - ]}> - <ErrorBoundary> - <Com - params={params} - navIdx={navIdx} - visible={current} - /> - </ErrorBoundary> - </Animated.View> - </Screen> - ) - }, - )} - </ScreenContainer> - <BottomBar /> - {isMenuActive || menuSwipingDirection !== 0 ? ( - <TouchableWithoutFeedback - onPress={() => store.shell.setMainMenuOpen(false)}> - <Animated.View style={[styles.screenMask, menuSwipeOpacity]} /> - </TouchableWithoutFeedback> - ) : undefined} - {shouldRenderMenu && ( - <Animated.View style={[styles.menuDrawer, menuSwipeTransform]}> - <Menu onClose={() => store.shell.setMainMenuOpen(false)} /> - </Animated.View> - )} - </HorzSwipe> - </View> - <ModalsContainer /> - <Lightbox /> - <Composer - active={store.shell.isComposerActive} - onClose={() => store.shell.closeComposer()} - winHeight={winDim.height} - replyTo={store.shell.composerOpts?.replyTo} - imagesOpen={store.shell.composerOpts?.imagesOpen} - onPost={store.shell.composerOpts?.onPost} - quote={store.shell.composerOpts?.quote} - /> - </View> - ) -}) - -/** - * This method produces the information needed by the shell to - * render the current screens with screen-caching behaviors. - */ -type ScreenRenderDesc = MatchResult & { - key: string - navIdx: string - current: boolean - previous: boolean - isNewTab: boolean -} -function constructScreenRenderDesc(nav: NavigationModel): { - icon: IconProp - hasNewTab: boolean - screens: ScreenRenderDesc[] -} { - let hasNewTab = false - let icon: IconProp = 'magnifying-glass' - let screens: ScreenRenderDesc[] = [] - for (const tab of nav.tabs) { - const tabScreens = [ - ...tab.getBackList(5), - Object.assign({}, tab.current, {index: tab.index}), - ] - const parsedTabScreens = tabScreens.map(screen => { - const isCurrent = nav.isCurrentScreen(tab.id, screen.index) - const isPrevious = nav.isCurrentScreen(tab.id, screen.index + 1) - const matchRes = match(screen.url) - if (isCurrent) { - icon = matchRes.icon - } - hasNewTab = hasNewTab || tab.isNewTab - return Object.assign(matchRes, { - key: `t${tab.id}-s${screen.index}`, - navIdx: `${tab.id}-${screen.id}`, - current: isCurrent, - previous: isPrevious, - isNewTab: tab.isNewTab, - }) as ScreenRenderDesc - }) - screens = screens.concat(parsedTabScreens) - } - return { - icon, - hasNewTab, - screens, - } -} - -const styles = StyleSheet.create({ - outerContainer: { - height: '100%', - }, - innerContainer: { - height: '100%', - }, - screenContainer: { - height: '100%', - }, - screenMask: { - position: 'absolute', - top: 0, - bottom: 0, - left: 0, - right: 0, - backgroundColor: '#000', - opacity: 0.6, - }, - menuDrawer: { - position: 'absolute', - top: 0, - bottom: 0, - left: 0, - right: 100, - }, - topBarProtector: { - position: 'absolute', - top: 0, - left: 0, - right: 0, - height: 50, // will be overwritten by insets - backgroundColor: colors.white, - }, - topBarProtectorDark: { - backgroundColor: colors.black, - }, -}) |