Move to expo and react-navigation (#288)

* WIP - adding expo

* WIP - adding expo 2

* Fix tsc

* Finish adding expo

* Disable the 'require cycle' warning

* Tweak plist

* Modify some dependency versions to make expo happy

* Fix icon fill

* Get Web compiling for expo

* 1.7

* Switch to react-navigation in expo2 (#287)

* WIP Switch to react-navigation

* WIP Switch to react-navigation 2

* WIP Switch to react-navigation 3

* Convert all screens to react navigation

* Update BottomBar for react navigation

* Update mobile menu to be react-native drawer

* Fixes to drawer and bottombar

* Factor out some helpers

* Replace the navigation model with react-navigation

* Restructure the shell folder and fix the header positioning

* Restore the error boundary

* Fix tsc

* Implement not-found page

* Remove react-native-gesture-handler (no longer used)

* Handle notifee card presses

* Handle all navigations from the state layer

* Fix drawer behaviors

* Fix two linking issues

* Switch to our react-native-progress fork to fix an svg rendering issue

* Get Web working with react-navigation

* Refactor routes and navigation for a bit more clarity

* Remove dead code

* Rework Web shell to left/right nav to make this easier

* Fix ViewHeader for desktop web

* Hide profileheader back btn on desktop web

* Move the compose button to the left nav

* Implement reply prompt in threads for desktop web

* Composer refactors

* Factor out all platform-specific text input behaviors from the composer

* Small fix

* Update the web build to use tiptap for the composer

* Tune up the mention autocomplete dropdown

* Simplify the default avatar and banner

* Fixes to link cards in web composer

* Fix dropdowns on web

* Tweak load latest on desktop

* Add web beta message and feedback link

* Fix up links in desktop web
This commit is contained in:
Paul Frazee 2023-03-13 16:01:43 -05:00 committed by GitHub
parent 503e03d91e
commit 56cf890deb
222 changed files with 8705 additions and 6338 deletions

View file

@ -0,0 +1,255 @@
import React from 'react'
import {
Animated,
GestureResponderEvent,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native'
import {StackActions, useNavigationState} from '@react-navigation/native'
import {BottomTabBarProps} from '@react-navigation/bottom-tabs'
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 {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'
import {getTabState, TabState} from 'lib/routes/helpers'
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(
state => {
return {
isAtHome: getTabState(state, 'Home') !== TabState.Outside,
isAtSearch: getTabState(state, 'Search') !== TabState.Outside,
isAtNotifications:
getTabState(state, 'Notifications') !== TabState.Outside,
}
},
)
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 onPressTab = React.useCallback(
(tab: string) => {
track(`MobileShell:${tab}ButtonPressed`)
const state = navigation.getState()
const tabState = getTabState(state, tab)
if (tabState === TabState.InsideAtRoot) {
store.emitScreenSoftReset()
} else if (tabState === TabState.Inside) {
navigation.dispatch(StackActions.popToTop())
} else {
navigation.navigate(`${tab}Tab`)
}
},
[store, track, navigation],
)
const onPressHome = React.useCallback(() => onPressTab('Home'), [onPressTab])
const onPressSearch = React.useCallback(
() => onPressTab('Search'),
[onPressTab],
)
const onPressNotifications = React.useCallback(
() => onPressTab('Notifications'),
[onPressTab],
)
const onPressProfile = React.useCallback(() => {
track('MobileShell:ProfileButtonPressed')
navigation.navigate('Profile', {name: store.me.handle})
}, [navigation, track, store.me.handle])
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,
},
})