Mobile Web (#427)
* WIP * WIP * Fix header offset on web * Remove debug * Fix web mobile feed and FAB layout * Fix modals on mobile web * Remove dead code * Remove ios config that shouldnt be committed now * Move bottom bar into its own folder * Fix web drawer navigation and state behaviors * Remove dark mode toggle from web drawer for now * Fix search on mobile web * Fix the logged out splash screen on mobile web * Fixes to detox simulator --------- Co-authored-by: Paul Frazee <pfrazee@gmail.com>
This commit is contained in:
parent
2fed6c4021
commit
f6769b283f
30 changed files with 4343 additions and 319 deletions
|
@ -4,6 +4,7 @@ import {StyleSheet, View} from 'react-native'
|
|||
import {ComposePost} from '../com/composer/Composer'
|
||||
import {ComposerOpts} from 'state/models/ui/shell'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {isMobileWeb} from 'platform/detection'
|
||||
|
||||
export const Composer = observer(
|
||||
({
|
||||
|
@ -60,7 +61,7 @@ const styles = StyleSheet.create({
|
|||
width: '100%',
|
||||
paddingVertical: 0,
|
||||
paddingHorizontal: 2,
|
||||
borderRadius: 8,
|
||||
borderRadius: isMobileWeb ? 0 : 8,
|
||||
marginBottom: '10vh',
|
||||
},
|
||||
})
|
||||
|
|
|
@ -8,11 +8,7 @@ import {
|
|||
View,
|
||||
ViewStyle,
|
||||
} from 'react-native'
|
||||
import {
|
||||
useNavigation,
|
||||
useNavigationState,
|
||||
StackActions,
|
||||
} from '@react-navigation/native'
|
||||
import {useNavigation, StackActions} from '@react-navigation/native'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import {
|
||||
FontAwesomeIcon,
|
||||
|
@ -40,6 +36,8 @@ import {useAnalytics} from 'lib/analytics'
|
|||
import {pluralize} from 'lib/strings/helpers'
|
||||
import {getTabState, TabState} from 'lib/routes/helpers'
|
||||
import {NavigationProp} from 'lib/routes/types'
|
||||
import {useNavigationTabState} from 'lib/hooks/useNavigationTabState'
|
||||
import {isWeb} from 'platform/detection'
|
||||
|
||||
export const DrawerContent = observer(() => {
|
||||
const theme = useTheme()
|
||||
|
@ -47,16 +45,7 @@ export const DrawerContent = observer(() => {
|
|||
const store = useStores()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const {track} = useAnalytics()
|
||||
const {isAtHome, isAtSearch, isAtNotifications} = useNavigationState(
|
||||
state => {
|
||||
return {
|
||||
isAtHome: getTabState(state, 'Home') !== TabState.Outside,
|
||||
isAtSearch: getTabState(state, 'Search') !== TabState.Outside,
|
||||
isAtNotifications:
|
||||
getTabState(state, 'Notifications') !== TabState.Outside,
|
||||
}
|
||||
},
|
||||
)
|
||||
const {isAtHome, isAtSearch, isAtNotifications} = useNavigationTabState()
|
||||
|
||||
// events
|
||||
// =
|
||||
|
@ -66,14 +55,19 @@ export const DrawerContent = observer(() => {
|
|||
track('Menu:ItemClicked', {url: tab})
|
||||
const state = navigation.getState()
|
||||
store.shell.closeDrawer()
|
||||
const tabState = getTabState(state, tab)
|
||||
if (tabState === TabState.InsideAtRoot) {
|
||||
store.emitScreenSoftReset()
|
||||
} else if (tabState === TabState.Inside) {
|
||||
navigation.dispatch(StackActions.popToTop())
|
||||
} else {
|
||||
if (isWeb) {
|
||||
// @ts-ignore must be Home, Search, or Notifications
|
||||
navigation.navigate(`${tab}Tab`)
|
||||
navigation.navigate(tab)
|
||||
} else {
|
||||
const tabState = getTabState(state, tab)
|
||||
if (tabState === TabState.InsideAtRoot) {
|
||||
store.emitScreenSoftReset()
|
||||
} else if (tabState === TabState.Inside) {
|
||||
navigation.dispatch(StackActions.popToTop())
|
||||
} else {
|
||||
// @ts-ignore must be Home, Search, or Notifications
|
||||
navigation.navigate(`${tab}Tab`)
|
||||
}
|
||||
}
|
||||
},
|
||||
[store, track, navigation],
|
||||
|
@ -240,20 +234,22 @@ export const DrawerContent = observer(() => {
|
|||
</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>
|
||||
{!isWeb && (
|
||||
<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={[
|
||||
|
|
|
@ -2,7 +2,6 @@ import React from 'react'
|
|||
import {
|
||||
Animated,
|
||||
GestureResponderEvent,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native'
|
||||
|
@ -13,7 +12,6 @@ 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 {clamp} from 'lib/numbers'
|
||||
import {
|
||||
HomeIcon,
|
||||
|
@ -24,14 +22,14 @@ import {
|
|||
BellIconSolid,
|
||||
UserIcon,
|
||||
} from 'lib/icons'
|
||||
import {colors} from 'lib/styles'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {getTabState, TabState} from 'lib/routes/helpers'
|
||||
import {styles} from './BottomBarStyles'
|
||||
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
|
||||
|
||||
export const BottomBar = observer(({navigation}: BottomTabBarProps) => {
|
||||
const store = useStores()
|
||||
const pal = usePalette('default')
|
||||
const minimalShellInterp = useAnimatedValue(0)
|
||||
const safeAreaInsets = useSafeAreaInsets()
|
||||
const {track} = useAnalytics()
|
||||
const {isAtHome, isAtSearch, isAtNotifications} = useNavigationState(
|
||||
|
@ -52,26 +50,7 @@ export const BottomBar = observer(({navigation}: BottomTabBarProps) => {
|
|||
},
|
||||
)
|
||||
|
||||
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 {footerMinimalShellTransform} = useMinimalShellMode()
|
||||
|
||||
const onPressTab = React.useCallback(
|
||||
(tab: string) => {
|
||||
|
@ -217,62 +196,3 @@ function Btn({
|
|||
</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: 8,
|
||||
backgroundColor: colors.blue3,
|
||||
paddingHorizontal: 4,
|
||||
paddingBottom: 1,
|
||||
borderRadius: 6,
|
||||
zIndex: 1,
|
||||
},
|
||||
notificationCountLight: {
|
||||
borderColor: colors.white,
|
||||
},
|
||||
notificationCountDark: {
|
||||
borderColor: colors.gray8,
|
||||
},
|
||||
notificationCountLabel: {
|
||||
fontSize: 12,
|
||||
fontWeight: 'bold',
|
||||
color: colors.white,
|
||||
fontVariant: ['tabular-nums'],
|
||||
},
|
||||
ctrlIcon: {
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
},
|
||||
ctrlIconSizingWrapper: {
|
||||
height: 27,
|
||||
},
|
||||
homeIcon: {
|
||||
top: 0,
|
||||
},
|
||||
searchIcon: {
|
||||
top: -2,
|
||||
},
|
||||
bellIcon: {
|
||||
top: -2.5,
|
||||
},
|
||||
profileIcon: {
|
||||
top: -4,
|
||||
},
|
||||
})
|
61
src/view/shell/bottom-bar/BottomBarStyles.tsx
Normal file
61
src/view/shell/bottom-bar/BottomBarStyles.tsx
Normal file
|
@ -0,0 +1,61 @@
|
|||
import {StyleSheet} from 'react-native'
|
||||
import {colors} from 'lib/styles'
|
||||
|
||||
export 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: 8,
|
||||
backgroundColor: colors.blue3,
|
||||
paddingHorizontal: 4,
|
||||
paddingBottom: 1,
|
||||
borderRadius: 6,
|
||||
zIndex: 1,
|
||||
},
|
||||
notificationCountLight: {
|
||||
borderColor: colors.white,
|
||||
},
|
||||
notificationCountDark: {
|
||||
borderColor: colors.gray8,
|
||||
},
|
||||
notificationCountLabel: {
|
||||
fontSize: 12,
|
||||
fontWeight: 'bold',
|
||||
color: colors.white,
|
||||
fontVariant: ['tabular-nums'],
|
||||
},
|
||||
ctrlIcon: {
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
},
|
||||
ctrlIconSizingWrapper: {
|
||||
height: 27,
|
||||
},
|
||||
homeIcon: {
|
||||
top: 0,
|
||||
},
|
||||
searchIcon: {
|
||||
top: -2,
|
||||
},
|
||||
bellIcon: {
|
||||
top: -2.5,
|
||||
},
|
||||
profileIcon: {
|
||||
top: -4,
|
||||
},
|
||||
})
|
101
src/view/shell/bottom-bar/BottomBarWeb.tsx
Normal file
101
src/view/shell/bottom-bar/BottomBarWeb.tsx
Normal file
|
@ -0,0 +1,101 @@
|
|||
import React from 'react'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import {useStores} from 'state/index'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {Animated} from 'react-native'
|
||||
import {useNavigationState} from '@react-navigation/native'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import {getCurrentRoute, isTab} from 'lib/routes/helpers'
|
||||
import {styles} from './BottomBarStyles'
|
||||
import {clamp} from 'lib/numbers'
|
||||
import {
|
||||
BellIcon,
|
||||
BellIconSolid,
|
||||
HomeIcon,
|
||||
HomeIconSolid,
|
||||
MagnifyingGlassIcon2,
|
||||
MagnifyingGlassIcon2Solid,
|
||||
UserIcon,
|
||||
} from 'lib/icons'
|
||||
import {Link} from 'view/com/util/Link'
|
||||
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
|
||||
|
||||
export const BottomBarWeb = observer(() => {
|
||||
const store = useStores()
|
||||
const pal = usePalette('default')
|
||||
const safeAreaInsets = useSafeAreaInsets()
|
||||
const {footerMinimalShellTransform} = useMinimalShellMode()
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.bottomBar,
|
||||
pal.view,
|
||||
pal.border,
|
||||
{paddingBottom: clamp(safeAreaInsets.bottom, 15, 30)},
|
||||
footerMinimalShellTransform,
|
||||
]}>
|
||||
<NavItem routeName="Home" href="/">
|
||||
{({isActive}) => {
|
||||
const Icon = isActive ? HomeIconSolid : HomeIcon
|
||||
return (
|
||||
<Icon
|
||||
strokeWidth={4}
|
||||
size={24}
|
||||
style={[styles.ctrlIcon, pal.text, styles.homeIcon]}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</NavItem>
|
||||
<NavItem routeName="Search" href="/search">
|
||||
{({isActive}) => {
|
||||
const Icon = isActive
|
||||
? MagnifyingGlassIcon2Solid
|
||||
: MagnifyingGlassIcon2
|
||||
return (
|
||||
<Icon
|
||||
size={25}
|
||||
style={[styles.ctrlIcon, pal.text, styles.searchIcon]}
|
||||
strokeWidth={1.8}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</NavItem>
|
||||
<NavItem routeName="Notifications" href="/notifications">
|
||||
{({isActive}) => {
|
||||
const Icon = isActive ? BellIconSolid : BellIcon
|
||||
return (
|
||||
<Icon
|
||||
size={24}
|
||||
strokeWidth={1.9}
|
||||
style={[styles.ctrlIcon, pal.text, styles.bellIcon]}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</NavItem>
|
||||
<NavItem routeName="Profile" href={`/profile/${store.me.handle}`}>
|
||||
{() => (
|
||||
<UserIcon
|
||||
size={28}
|
||||
strokeWidth={1.5}
|
||||
style={[styles.ctrlIcon, pal.text, styles.profileIcon]}
|
||||
/>
|
||||
)}
|
||||
</NavItem>
|
||||
</Animated.View>
|
||||
)
|
||||
})
|
||||
|
||||
const NavItem: React.FC<{
|
||||
children: (props: {isActive: boolean}) => React.ReactChild
|
||||
href: string
|
||||
routeName: string
|
||||
}> = ({children, href, routeName}) => {
|
||||
const currentRoute = useNavigationState(getCurrentRoute)
|
||||
const isActive = isTab(currentRoute.name, routeName)
|
||||
return (
|
||||
<Link href={href} style={styles.ctrl}>
|
||||
{children({isActive})}
|
||||
</Link>
|
||||
)
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import {View, StyleSheet} from 'react-native'
|
||||
import {View, StyleSheet, TouchableOpacity} from 'react-native'
|
||||
import {useStores} from 'state/index'
|
||||
import {DesktopLeftNav} from './desktop/LeftNav'
|
||||
import {DesktopRightNav} from './desktop/RightNav'
|
||||
|
@ -11,9 +11,13 @@ import {Composer} from './Composer.web'
|
|||
import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle'
|
||||
import {s, colors} from 'lib/styles'
|
||||
import {RoutesContainer, FlatNavigator} from '../../Navigation'
|
||||
import {DrawerContent} from './Drawer'
|
||||
import {useWebMediaQueries} from '../../lib/hooks/useWebMediaQueries'
|
||||
import {BottomBarWeb} from './bottom-bar/BottomBarWeb'
|
||||
|
||||
const ShellInner = observer(() => {
|
||||
const store = useStores()
|
||||
const {isDesktop} = useWebMediaQueries()
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -22,10 +26,14 @@ const ShellInner = observer(() => {
|
|||
<FlatNavigator />
|
||||
</ErrorBoundary>
|
||||
</View>
|
||||
<DesktopLeftNav />
|
||||
<DesktopRightNav />
|
||||
<View style={[styles.viewBorder, styles.viewBorderLeft]} />
|
||||
<View style={[styles.viewBorder, styles.viewBorderRight]} />
|
||||
{isDesktop && (
|
||||
<>
|
||||
<DesktopLeftNav />
|
||||
<DesktopRightNav />
|
||||
<View style={[styles.viewBorder, styles.viewBorderLeft]} />
|
||||
<View style={[styles.viewBorder, styles.viewBorderRight]} />
|
||||
</>
|
||||
)}
|
||||
<Composer
|
||||
active={store.shell.isComposerActive}
|
||||
onClose={() => store.shell.closeComposer()}
|
||||
|
@ -34,8 +42,18 @@ const ShellInner = observer(() => {
|
|||
quote={store.shell.composerOpts?.quote}
|
||||
onPost={store.shell.composerOpts?.onPost}
|
||||
/>
|
||||
{!isDesktop && <BottomBarWeb />}
|
||||
<ModalsContainer />
|
||||
<Lightbox />
|
||||
{!isDesktop && store.shell.isDrawerOpen && (
|
||||
<TouchableOpacity
|
||||
onPress={() => store.shell.closeDrawer()}
|
||||
style={styles.drawerMask}>
|
||||
<View style={styles.drawerContainer}>
|
||||
<DrawerContent />
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
@ -71,4 +89,19 @@ const styles = StyleSheet.create({
|
|||
viewBorderRight: {
|
||||
left: 'calc(50vw + 300px)',
|
||||
},
|
||||
drawerMask: {
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
top: 0,
|
||||
left: 0,
|
||||
backgroundColor: 'rgba(0,0,0,0.25)',
|
||||
},
|
||||
drawerContainer: {
|
||||
display: 'flex',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
height: '100%',
|
||||
},
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue