tooot/src/Screens.tsx

252 lines
7.9 KiB
TypeScript

import { HeaderLeft } from '@components/Header'
import { displayMessage, Message, removeMessage } from '@components/Message'
import navigationRef from '@helpers/navigationRef'
import { useNetInfo } from '@react-native-community/netinfo'
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import ScreenActions from '@screens/Actions'
import ScreenAnnouncements from '@screens/Announcements'
import ScreenCompose from '@screens/Compose'
import ScreenImagesViewer from '@screens/ImagesViewer'
import ScreenTabs from '@screens/Tabs'
import initQuery from '@utils/initQuery'
import { RootStackParamList } from '@utils/navigation/navigators'
import pushUseConnect from '@utils/push/useConnect'
import pushUseReceive from '@utils/push/useReceive'
import pushUseRespond from '@utils/push/useRespond'
import { updatePreviousTab } from '@utils/slices/contextsSlice'
import { updateAccountPreferences } from '@utils/slices/instances/updateAccountPreferences'
import { updateConfiguration } from '@utils/slices/instances/updateConfiguration'
import { updateFilters } from '@utils/slices/instances/updateFilters'
import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice'
import { useTheme } from '@utils/styles/ThemeManager'
import { themes } from '@utils/styles/themes'
import * as Analytics from 'expo-firebase-analytics'
import * as Linking from 'expo-linking'
import { addScreenshotListener } from 'expo-screen-capture'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Alert, Platform, StatusBar } from 'react-native'
import { useQueryClient } from 'react-query'
import { useDispatch, useSelector } from 'react-redux'
import * as Sentry from 'sentry-expo'
const Stack = createNativeStackNavigator<RootStackParamList>()
export interface Props {
localCorrupt?: string
}
const Screens: React.FC<Props> = ({ localCorrupt }) => {
const { t } = useTranslation('screens')
const dispatch = useDispatch()
const instanceActive = useSelector(getInstanceActive)
const { mode, theme } = useTheme()
enum barStyle {
light = 'dark-content',
dark = 'light-content'
}
const routeRef = useRef<{ name?: string; params?: {} }>()
const isConnected = useNetInfo().isConnected
useEffect(() => {
switch (isConnected) {
case true:
removeMessage()
break
case false:
displayMessage({
mode,
type: 'error',
message: t('network.disconnected.message'),
description: t('network.disconnected.description'),
autoHide: false
})
break
}
}, [isConnected])
// Push hooks
const instances = useSelector(
getInstances,
(prev, next) => prev.length === next.length
)
const queryClient = useQueryClient()
pushUseConnect({ t, instances })
pushUseReceive({ instances })
pushUseRespond({ instances })
// Prevent screenshot alert
useEffect(() => {
const screenshotListener = addScreenshotListener(() =>
Alert.alert(t('screenshot.title'), t('screenshot.message'), [
{ text: t('screenshot.button'), style: 'destructive' }
])
)
Platform.select({ ios: screenshotListener })
return () => screenshotListener.remove()
}, [])
// On launch display login credentials corrupt information
useEffect(() => {
const showLocalCorrect = () => {
if (localCorrupt) {
displayMessage({
message: t('localCorrupt.message'),
description: localCorrupt.length ? localCorrupt : undefined,
type: 'error',
mode
})
navigationRef.navigate('Screen-Tabs', {
screen: 'Tab-Me'
})
}
}
return showLocalCorrect()
}, [localCorrupt])
// Lazily update users's preferences, for e.g. composing default visibility
useEffect(() => {
if (instanceActive !== -1) {
dispatch(updateConfiguration())
dispatch(updateFilters())
dispatch(updateAccountPreferences())
}
}, [instanceActive])
// Callbacks
const navigationContainerOnReady = useCallback(() => {
const currentRoute = navigationRef.getCurrentRoute()
routeRef.current = {
name: currentRoute?.name,
params: currentRoute?.params
? JSON.stringify(currentRoute.params)
: undefined
}
}, [])
const navigationContainerOnStateChange = useCallback(() => {
const previousRoute = routeRef.current
const currentRoute = navigationRef.getCurrentRoute()
const matchTabName = currentRoute?.name?.match(/(Tab-.*)-Root/)
if (matchTabName) {
//@ts-ignore
dispatch(updatePreviousTab(matchTabName[1]))
}
if (previousRoute?.name !== currentRoute?.name) {
Analytics.logEvent('screen_view', { screen_name: currentRoute?.name })
Sentry.Native.setContext('page', {
previous: previousRoute,
current: currentRoute
})
}
routeRef.current = currentRoute
}, [])
// Deep linking for compose
const [deeplinked, setDeeplinked] = useState(false)
useEffect(() => {
const getUrlAsync = async () => {
setDeeplinked(true)
const initialUrl = await Linking.parseInitialURLAsync()
if (initialUrl.path) {
const paths = initialUrl.path.split('/')
if (paths && paths.length) {
const instanceIndex = instances.findIndex(
instance => paths[0] === `@${instance.account.acct}@${instance.uri}`
)
if (instanceIndex !== -1 && instanceActive !== instanceIndex) {
initQuery({
instance: instances[instanceIndex],
prefetch: { enabled: true }
})
}
}
}
if (initialUrl.hostname === 'compose') {
navigationRef.navigate('Screen-Compose')
}
}
if (!deeplinked) {
getUrlAsync()
}
}, [instanceActive, instances, deeplinked])
return (
<>
<StatusBar
barStyle={barStyle[mode]}
backgroundColor={theme.backgroundDefault}
/>
<NavigationContainer
ref={navigationRef}
theme={themes[mode]}
onReady={navigationContainerOnReady}
onStateChange={navigationContainerOnStateChange}
>
<Stack.Navigator initialRouteName='Screen-Tabs'>
<Stack.Screen
name='Screen-Tabs'
component={ScreenTabs}
options={{ headerShown: false }}
/>
<Stack.Screen
name='Screen-Actions'
component={ScreenActions}
options={{
presentation: 'transparentModal',
animation: 'fade',
headerShown: false
}}
/>
<Stack.Screen
name='Screen-Announcements'
component={ScreenAnnouncements}
options={({ navigation }) => ({
presentation: 'transparentModal',
animation: 'fade',
headerShown: true,
headerShadowVisible: false,
headerTransparent: true,
headerStyle: { backgroundColor: 'transparent' },
headerLeft: () => (
<HeaderLeft content='X' onPress={() => navigation.goBack()} />
),
title: t('screenAnnouncements:heading')
})}
/>
<Stack.Screen
name='Screen-Compose'
component={ScreenCompose}
options={{
headerShown: false,
presentation: 'fullScreenModal'
}}
/>
<Stack.Screen
name='Screen-ImagesViewer'
component={ScreenImagesViewer}
options={{
headerShown: false,
presentation: 'fullScreenModal',
animation: 'fade'
}}
/>
</Stack.Navigator>
<Message />
</NavigationContainer>
</>
)
}
export default React.memo(Screens, () => true)