tooot/src/Index.tsx

325 lines
9.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import client from '@api/client'
import haptics from '@components/haptics'
import Icon from '@components/Icon'
import { toast, toastConfig } from '@components/toast'
import {
BottomTabNavigationOptions,
createBottomTabNavigator
} from '@react-navigation/bottom-tabs'
import {
getFocusedRouteNameFromRoute,
NavigationContainer,
NavigationContainerRef
} from '@react-navigation/native'
import ScreenLocal from '@screens/Local'
import ScreenMe from '@screens/Me'
import ScreenNotifications from '@screens/Notifications'
import ScreenPublic from '@screens/Public'
import { useTimelineQuery } from '@utils/queryHooks/timeline'
import {
getLocalAccount,
getLocalActiveIndex,
getLocalNotification,
localUpdateAccountPreferences,
localUpdateNotification
} from '@utils/slices/instancesSlice'
import { useTheme } from '@utils/styles/ThemeManager'
import { themes } from '@utils/styles/themes'
import * as Analytics from 'expo-firebase-analytics'
import React, {
createRef,
useCallback,
useEffect,
useMemo,
useRef
} from 'react'
import { useTranslation } from 'react-i18next'
import { Image, Platform, StatusBar } from 'react-native'
import Toast from 'react-native-toast-message'
import { useDispatch, useSelector } from 'react-redux'
const Tab = createBottomTabNavigator<Nav.RootStackParamList>()
export interface Props {
localCorrupt?: string
}
export const navigationRef = createRef<NavigationContainerRef>()
const Index: React.FC<Props> = ({ localCorrupt }) => {
const dispatch = useDispatch()
const localActiveIndex = useSelector(getLocalActiveIndex)
const { mode, theme } = useTheme()
enum barStyle {
light = 'dark-content',
dark = 'light-content'
}
const routeNameRef = useRef<string | undefined>()
// const isConnected = useNetInfo().isConnected
// const [firstRender, setFirstRender] = useState(false)
// useEffect(() => {
// if (firstRender) {
// // bug in netInfo on first render as false
// if (isConnected !== false) {
// toast({ type: 'error', content: 'æ‰æœºðŸˆšï¸<C3AF>ç½ç»œ', autoHide: false })
// }
// } else {
// setFirstRender(true)
// }
// }, [isConnected, firstRender])
// On launch display login credentials corrupt information
const { t } = useTranslation('common')
useEffect(() => {
const showLocalCorrect = localCorrupt
? toast({
type: 'error',
message: t('index.localCorrupt'),
description: localCorrupt.length ? localCorrupt : undefined,
autoHide: false
})
: undefined
return showLocalCorrect
}, [localCorrupt])
// On launch check if there is any unread announcements
useEffect(() => {
console.log('Checking announcements')
localActiveIndex !== null &&
client<Mastodon.Announcement[]>({
method: 'get',
instance: 'local',
url: `announcements`
})
.then(res => {
if (res?.filter(announcement => !announcement.read).length) {
navigationRef.current?.navigate('Screen-Shared-Announcements', {
showAll: false
})
}
})
.catch(() => {})
}, [])
// On launch check if there is any unread noficiations
const queryNotification = useTimelineQuery({
page: 'Notifications',
options: {
enabled: localActiveIndex !== null ? true : false,
refetchInterval: 1000 * 60,
refetchIntervalInBackground: true
}
})
const prevNotification = useSelector(getLocalNotification)
useEffect(() => {
if (queryNotification.data?.pages) {
const flattenData = queryNotification.data.pages.flatMap(d => [...d])
const latestNotificationTime = flattenData.length
? (flattenData[0] as Mastodon.Notification).created_at
: undefined
if (!prevNotification || !prevNotification.latestTime) {
dispatch(
localUpdateNotification({
unread: false
})
)
} else if (
latestNotificationTime &&
new Date(prevNotification.latestTime) < new Date(latestNotificationTime)
) {
dispatch(
localUpdateNotification({
unread: true,
latestTime: latestNotificationTime
})
)
}
}
}, [queryNotification.data?.pages])
// Lazily update users's preferences, for e.g. composing default visibility
useEffect(() => {
if (localActiveIndex !== null) {
dispatch(localUpdateAccountPreferences())
}
}, [])
// Callbacks
const navigationContainerOnReady = useCallback(
() =>
(routeNameRef.current = navigationRef.current?.getCurrentRoute()?.name),
[]
)
const navigationContainerOnStateChange = useCallback(() => {
const previousRouteName = routeNameRef.current
const currentRouteName = navigationRef.current?.getCurrentRoute()?.name
if (previousRouteName !== currentRouteName) {
Analytics.setCurrentScreen(currentRouteName)
}
routeNameRef.current = currentRouteName
}, [])
const localAccount = useSelector(getLocalAccount)
const tabNavigatorScreenOptions = useCallback(
({ route }): BottomTabNavigationOptions => ({
tabBarIcon: ({
focused,
color,
size
}: {
focused: boolean
color: string
size: number
}) => {
switch (route.name) {
case 'Screen-Local':
return <Icon name='Home' size={size} color={color} />
case 'Screen-Public':
return <Icon name='Globe' size={size} color={color} />
case 'Screen-Post':
return <Icon name='Plus' size={size} color={color} />
case 'Screen-Notifications':
return <Icon name='Bell' size={size} color={color} />
case 'Screen-Me':
return localActiveIndex !== null ? (
<Image
source={{ uri: localAccount?.avatarStatic }}
style={{
width: size,
height: size,
borderRadius: size,
borderWidth: focused ? 2 : 0,
borderColor: focused ? theme.secondary : color
}}
/>
) : (
<Icon
name={focused ? 'Meh' : 'Smile'}
size={size}
color={!focused ? theme.secondary : color}
/>
)
default:
return <Icon name='AlertOctagon' size={size} color={color} />
}
},
...(Platform.OS === 'android' && {
tabBarVisible:
getFocusedRouteNameFromRoute(route) !== 'Screen-Shared-Compose' &&
getFocusedRouteNameFromRoute(route) !==
'Screen-Shared-Announcements' &&
getFocusedRouteNameFromRoute(route) !==
'Screen-Shared-ImagesViewer' &&
getFocusedRouteNameFromRoute(route) !== 'Screen-Me-Switch'
})
}),
[localActiveIndex, localAccount]
)
const tabNavigatorTabBarOptions = useMemo(
() => ({
activeTintColor: theme.primary,
inactiveTintColor:
localActiveIndex !== null ? theme.secondary : theme.disabled,
showLabel: false,
...(Platform.OS === 'android' && { keyboardHidesTabBar: true })
}),
[theme, localActiveIndex]
)
const tabScreenLocalListeners = useCallback(
() => ({
tabPress: (e: any) => {
if (!(localActiveIndex !== null)) {
e.preventDefault()
}
}
}),
[localActiveIndex]
)
const tabScreenComposeListeners = useMemo(
() => ({
tabPress: (e: any) => {
e.preventDefault()
if (localActiveIndex !== null) {
haptics('Medium')
navigationRef.current?.navigate('Screen-Shared-Compose')
}
}
}),
[localActiveIndex]
)
const tabScreenComposeComponent = useCallback(() => null, [])
const tabScreenNotificationsListeners = useCallback(
() => ({
tabPress: (e: any) => {
if (!(localActiveIndex !== null)) {
e.preventDefault()
}
}
}),
[localActiveIndex]
)
return (
<>
<StatusBar barStyle={barStyle[mode]} backgroundColor={theme.background} />
<NavigationContainer
ref={navigationRef}
theme={themes[mode]}
onReady={navigationContainerOnReady}
onStateChange={navigationContainerOnStateChange}
>
<Tab.Navigator
initialRouteName={
localActiveIndex !== null ? 'Screen-Local' : 'Screen-Me'
}
screenOptions={tabNavigatorScreenOptions}
tabBarOptions={tabNavigatorTabBarOptions}
>
<Tab.Screen
name='Screen-Local'
component={ScreenLocal}
listeners={tabScreenLocalListeners}
/>
<Tab.Screen name='Screen-Public' component={ScreenPublic} />
<Tab.Screen
name='Screen-Post'
component={tabScreenComposeComponent}
listeners={tabScreenComposeListeners}
/>
<Tab.Screen
name='Screen-Notifications'
component={ScreenNotifications}
listeners={tabScreenNotificationsListeners}
options={
prevNotification && prevNotification.unread
? {
tabBarBadge: '',
tabBarBadgeStyle: {
transform: [{ scale: 0.5 }],
backgroundColor: theme.red
}
}
: {
tabBarBadgeStyle: {
transform: [{ scale: 0.5 }],
backgroundColor: theme.red
}
}
}
/>
<Tab.Screen name='Screen-Me' component={ScreenMe} />
</Tab.Navigator>
{Platform.OS === 'ios' ? (
<Toast ref={Toast.setRef} config={toastConfig} />
) : null}
</NavigationContainer>
</>
)
}
export default React.memo(Index, () => true)