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 (
Update required
Please update your app to the latest version. If no update is
available yet, please check the App Store in a day or so.
What's happening?
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.
If we didn't botch this process, a new version of the app should
be available now.
)
}
if (!store.session.hasSession) {
return (
)
}
const screenBg = {
backgroundColor: theme.colorScheme === 'dark' ? colors.black : colors.gray1,
}
return (
{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 (
)
},
)}
{isMenuActive || menuSwipingDirection !== 0 ? (
store.shell.setMainMenuOpen(false)}>
) : undefined}
{shouldRenderMenu && (
)}
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}
/>
)
})
/**
* 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,
},
})