diff --git a/native/chat/thread-screen-pruner.react.js b/native/chat/thread-screen-pruner.react.js index c417ab00e..d837b6e8d 100644 --- a/native/chat/thread-screen-pruner.react.js +++ b/native/chat/thread-screen-pruner.react.js @@ -1,108 +1,116 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { Alert } from 'react-native'; import { threadIsPending } from 'lib/shared/thread-utils'; import { clearThreadsActionType } from '../navigation/action-types'; import { useActiveThread } from '../navigation/nav-selectors'; import { NavContext } from '../navigation/navigation-context'; import { getThreadIDFromRoute, getChildRouteFromNavigatorRoute, } from '../navigation/navigation-utils'; import { AppRouteName, ChatRouteName, + CommunityDrawerNavigatorRouteName, TabNavigatorRouteName, } from '../navigation/route-names'; import { useSelector } from '../redux/redux-utils'; import type { AppState } from '../redux/state-types'; const ThreadScreenPruner: React.ComponentType<{}> = React.memo<{}>( function ThreadScreenPruner() { const rawThreadInfos = useSelector( (state: AppState) => state.threadStore.threadInfos, ); const navContext = React.useContext(NavContext); const chatRouteState = React.useMemo(() => { if (!navContext) { return null; } const { state } = navContext; const appRoute = state.routes.find(route => route.name === AppRouteName); invariant( appRoute, 'Navigation context should contain route for AppNavigator ' + 'when ThreadScreenPruner is rendered', ); - const tabRoute = getChildRouteFromNavigatorRoute( + const communityDrawerRoute = getChildRouteFromNavigatorRoute( appRoute, + CommunityDrawerNavigatorRouteName, + ); + if (!communityDrawerRoute) { + return null; + } + const tabRoute = getChildRouteFromNavigatorRoute( + communityDrawerRoute, TabNavigatorRouteName, ); if (!tabRoute) { return null; } const chatRoute = getChildRouteFromNavigatorRoute( tabRoute, ChatRouteName, ); if (!chatRoute?.state) { return null; } return chatRoute.state; }, [navContext]); const inStackThreadIDs = React.useMemo(() => { const threadIDs = new Set(); if (!chatRouteState) { return threadIDs; } for (const route of chatRouteState.routes) { const threadID = getThreadIDFromRoute(route); if (threadID && !threadIsPending(threadID)) { threadIDs.add(threadID); } } return threadIDs; }, [chatRouteState]); const pruneThreadIDs = React.useMemo(() => { const threadIDs = []; for (const threadID of inStackThreadIDs) { if (!rawThreadInfos[threadID]) { threadIDs.push(threadID); } } return threadIDs; }, [inStackThreadIDs, rawThreadInfos]); const activeThreadID = useActiveThread(); React.useEffect(() => { if (pruneThreadIDs.length === 0 || !navContext) { return; } if (activeThreadID && pruneThreadIDs.includes(activeThreadID)) { Alert.alert( 'Chat invalidated', 'You no longer have permission to view this chat :(', [{ text: 'OK' }], { cancelable: true }, ); } navContext.dispatch({ type: clearThreadsActionType, payload: { threadIDs: pruneThreadIDs }, }); }, [pruneThreadIDs, navContext, activeThreadID]); return null; }, ); export default ThreadScreenPruner; diff --git a/native/navigation/app-navigator.react.js b/native/navigation/app-navigator.react.js index ca2022cf2..64bf62de5 100644 --- a/native/navigation/app-navigator.react.js +++ b/native/navigation/app-navigator.react.js @@ -1,144 +1,147 @@ // @flow import * as SplashScreen from 'expo-splash-screen'; import * as React from 'react'; import { useSelector } from 'react-redux'; import { PersistGate } from 'redux-persist/integration/react'; import MultimediaMessageTooltipModal from '../chat/multimedia-message-tooltip-modal.react'; import RobotextMessageTooltipModal from '../chat/robotext-message-tooltip-modal.react'; import ThreadSettingsMemberTooltipModal from '../chat/settings/thread-settings-member-tooltip-modal.react'; import TextMessageTooltipModal from '../chat/text-message-tooltip-modal.react'; import KeyboardStateContainer from '../keyboard/keyboard-state-container.react'; import CameraModal from '../media/camera-modal.react'; import ImageModal from '../media/image-modal.react'; import VideoPlaybackModal from '../media/video-playback-modal.react'; import RelationshipListItemTooltipModal from '../profile/relationship-list-item-tooltip-modal.react'; import PushHandler from '../push/push-handler.react'; import { getPersistor } from '../redux/persist'; import { RootContext } from '../root-context'; import { useLoadCommFonts } from '../themes/fonts'; import { waitForInteractions } from '../utils/timers'; import ActionResultModal from './action-result-modal.react'; +import { CommunityDrawerNavigator } from './community-drawer-navigator.react'; import { createOverlayNavigator } from './overlay-navigator.react'; import type { OverlayNavigationProp, OverlayNavigationHelpers, } from './overlay-navigator.react'; import type { RootNavigationProp } from './root-navigator.react'; import { - TabNavigatorRouteName, ImageModalRouteName, MultimediaMessageTooltipModalRouteName, ActionResultModalRouteName, TextMessageTooltipModalRouteName, ThreadSettingsMemberTooltipModalRouteName, RelationshipListItemTooltipModalRouteName, RobotextMessageTooltipModalRouteName, CameraModalRouteName, VideoPlaybackModalRouteName, + CommunityDrawerNavigatorRouteName, type ScreenParamList, type OverlayParamList, } from './route-names'; -import TabNavigator from './tab-navigator.react'; let splashScreenHasHidden = false; export type AppNavigationProp< RouteName: $Keys = $Keys, > = OverlayNavigationProp; const App = createOverlayNavigator< ScreenParamList, OverlayParamList, OverlayNavigationHelpers, >(); type AppNavigatorProps = { navigation: RootNavigationProp<'App'>, ... }; function AppNavigator(props: AppNavigatorProps): React.Node { const { navigation } = props; const fontsLoaded = useLoadCommFonts(); const rootContext = React.useContext(RootContext); const storeLoadedFromLocalDatabase = useSelector(state => state.storeLoaded); const setNavStateInitialized = rootContext && rootContext.setNavStateInitialized; React.useEffect(() => { setNavStateInitialized && setNavStateInitialized(); }, [setNavStateInitialized]); const [ localSplashScreenHasHidden, setLocalSplashScreenHasHidden, ] = React.useState(splashScreenHasHidden); React.useEffect(() => { if (localSplashScreenHasHidden || !fontsLoaded) { return; } splashScreenHasHidden = true; (async () => { await waitForInteractions(); try { await SplashScreen.hideAsync(); } finally { setLocalSplashScreenHasHidden(true); } })(); }, [localSplashScreenHasHidden, fontsLoaded]); let pushHandler; if (localSplashScreenHasHidden) { pushHandler = ( ); } if (!storeLoadedFromLocalDatabase) { return null; } return ( - + {pushHandler} ); } export default AppNavigator; diff --git a/native/navigation/community-drawer-navigator.react.js b/native/navigation/community-drawer-navigator.react.js index 4c4de0102..40fe7bcb1 100644 --- a/native/navigation/community-drawer-navigator.react.js +++ b/native/navigation/community-drawer-navigator.react.js @@ -1,55 +1,71 @@ // @flow -import { createDrawerNavigator } from '@react-navigation/drawer'; +import { + createDrawerNavigator, + type DrawerNavigationHelpers, + type DrawerNavigationProp, +} from '@react-navigation/drawer'; import * as React from 'react'; import { Dimensions, View } from 'react-native'; import { useStyles } from '../themes/colors'; import type { AppNavigationProp } from './app-navigator.react'; import CommunityDrawerContent from './community-drawer-content.react'; import { TabNavigatorRouteName } from './route-names'; -import type { NavigationRoute } from './route-names'; +import type { + NavigationRoute, + ScreenParamList, + CommunityDrawerParamList, +} from './route-names'; import TabNavigator from './tab-navigator.react'; -const Drawer = createDrawerNavigator(); - const communityDrawerContent = () => ; +export type CommunityDrawerNavigationProp< + RouteName: $Keys = $Keys, +> = DrawerNavigationProp; + +const Drawer = createDrawerNavigator< + ScreenParamList, + CommunityDrawerParamList, + DrawerNavigationHelpers, +>(); + type Props = { +navigation: AppNavigationProp<'CommunityDrawerNavigator'>, +route: NavigationRoute<'CommunityDrawerNavigator'>, }; // eslint-disable-next-line no-unused-vars function CommunityDrawerNavigator(props: Props): React.Node { const styles = useStyles(unboundStyles); const screenOptions = React.useMemo( () => ({ drawerStyle: styles.drawerStyle, }), [styles.drawerStyle], ); return ( ); } const unboundStyles = { drawerView: { flex: 1, }, drawerStyle: { width: Dimensions.get('window').width - 36, }, }; export { CommunityDrawerNavigator }; diff --git a/native/navigation/default-state.js b/native/navigation/default-state.js index a09faf2d1..a9d97bbe9 100644 --- a/native/navigation/default-state.js +++ b/native/navigation/default-state.js @@ -1,83 +1,93 @@ // @flow import type { StaleNavigationState } from '@react-navigation/native'; import type { BaseNavInfo } from 'lib/types/nav-types'; import { fifteenDaysEarlier, fifteenDaysLater } from 'lib/utils/date-utils'; import { AppRouteName, TabNavigatorRouteName, LoggedOutModalRouteName, ProfileRouteName, ProfileScreenRouteName, ChatRouteName, ChatThreadListRouteName, HomeChatThreadListRouteName, BackgroundChatThreadListRouteName, AppsRouteName, + CommunityDrawerNavigatorRouteName, } from './route-names'; export type NavInfo = $Exact; const defaultNavigationState: StaleNavigationState = { type: 'stack', index: 1, routes: [ { name: AppRouteName, state: { type: 'stack', index: 0, routes: [ { - name: TabNavigatorRouteName, + name: CommunityDrawerNavigatorRouteName, state: { - type: 'tab', + type: 'drawer', index: 0, routes: [ { - name: ChatRouteName, + name: TabNavigatorRouteName, state: { - type: 'stack', + type: 'tab', index: 0, routes: [ { - name: ChatThreadListRouteName, + name: ChatRouteName, state: { - type: 'tab', + type: 'stack', index: 0, routes: [ - { name: HomeChatThreadListRouteName }, - { name: BackgroundChatThreadListRouteName }, + { + name: ChatThreadListRouteName, + state: { + type: 'tab', + index: 0, + routes: [ + { name: HomeChatThreadListRouteName }, + { name: BackgroundChatThreadListRouteName }, + ], + }, + }, ], }, }, + { + name: ProfileRouteName, + state: { + type: 'stack', + index: 0, + routes: [{ name: ProfileScreenRouteName }], + }, + }, + { name: AppsRouteName }, ], }, }, - { - name: ProfileRouteName, - state: { - type: 'stack', - index: 0, - routes: [{ name: ProfileScreenRouteName }], - }, - }, - { name: AppsRouteName }, ], }, }, ], }, }, { name: LoggedOutModalRouteName }, ], }; const defaultNavInfo: NavInfo = { startDate: fifteenDaysEarlier().valueOf(), endDate: fifteenDaysLater().valueOf(), }; export { defaultNavigationState, defaultNavInfo }; diff --git a/native/navigation/nav-selectors.js b/native/navigation/nav-selectors.js index 3521dde77..da095677c 100644 --- a/native/navigation/nav-selectors.js +++ b/native/navigation/nav-selectors.js @@ -1,278 +1,289 @@ // @flow import type { PossiblyStaleNavigationState } from '@react-navigation/native'; import _memoize from 'lodash/memoize'; import * as React from 'react'; import { createSelector } from 'reselect'; import { nonThreadCalendarFiltersSelector } from 'lib/selectors/calendar-filter-selectors'; import { currentCalendarQuery } from 'lib/selectors/nav-selectors'; import type { CalendarQuery } from 'lib/types/entry-types'; import type { CalendarFilter } from 'lib/types/filter-types'; import { useSelector } from '../redux/redux-utils'; import type { NavPlusRedux } from '../types/selector-types'; import type { GlobalTheme } from '../types/themes'; import type { NavContextType } from './navigation-context'; import { NavContext } from './navigation-context'; import { getStateFromNavigatorRoute, getThreadIDFromRoute, } from './navigation-utils'; import { AppRouteName, TabNavigatorRouteName, MessageListRouteName, ChatRouteName, CalendarRouteName, ThreadPickerModalRouteName, ActionResultModalRouteName, accountModals, scrollBlockingModals, chatRootModals, threadRoutes, + CommunityDrawerNavigatorRouteName, } from './route-names'; const baseCreateIsForegroundSelector = (routeName: string) => createSelector( (context: ?NavContextType) => context && context.state, (navigationState: ?PossiblyStaleNavigationState) => { if (!navigationState) { return false; } return navigationState.routes[navigationState.index].name === routeName; }, ); const createIsForegroundSelector: ( routeName: string, ) => (context: ?NavContextType) => boolean = _memoize( baseCreateIsForegroundSelector, ); function useIsAppLoggedIn(): boolean { const navContext = React.useContext(NavContext); return React.useMemo(() => { if (!navContext) { return false; } const { state } = navContext; return !accountModals.includes(state.routes[state.index].name); }, [navContext]); } const baseCreateActiveTabSelector = (routeName: string) => createSelector( (context: ?NavContextType) => context && context.state, (navigationState: ?PossiblyStaleNavigationState) => { if (!navigationState) { return false; } const currentRootSubroute = navigationState.routes[navigationState.index]; if (currentRootSubroute.name !== AppRouteName) { return false; } const appState = getStateFromNavigatorRoute(currentRootSubroute); const [firstAppSubroute] = appState.routes; - if (firstAppSubroute.name !== TabNavigatorRouteName) { + if (firstAppSubroute.name !== CommunityDrawerNavigatorRouteName) { return false; } - const tabState = getStateFromNavigatorRoute(firstAppSubroute); + const communityDrawerState = getStateFromNavigatorRoute(firstAppSubroute); + const [firstCommunityDrawerSubroute] = communityDrawerState.routes; + if (firstCommunityDrawerSubroute.name !== TabNavigatorRouteName) { + return false; + } + const tabState = getStateFromNavigatorRoute(firstCommunityDrawerSubroute); return tabState.routes[tabState.index].name === routeName; }, ); const createActiveTabSelector: ( routeName: string, ) => (context: ?NavContextType) => boolean = _memoize( baseCreateActiveTabSelector, ); const scrollBlockingModalsClosedSelector: ( context: ?NavContextType, ) => boolean = createSelector( (context: ?NavContextType) => context && context.state, (navigationState: ?PossiblyStaleNavigationState) => { if (!navigationState) { return false; } const currentRootSubroute = navigationState.routes[navigationState.index]; if (currentRootSubroute.name !== AppRouteName) { return true; } const appState = getStateFromNavigatorRoute(currentRootSubroute); for (let i = appState.index; i >= 0; i--) { const route = appState.routes[i]; if (scrollBlockingModals.includes(route.name)) { return false; } } return true; }, ); function selectBackgroundIsDark( navigationState: ?PossiblyStaleNavigationState, theme: ?GlobalTheme, ): boolean { if (!navigationState) { return false; } const currentRootSubroute = navigationState.routes[navigationState.index]; if (currentRootSubroute.name !== AppRouteName) { // Very bright... we'll call it non-dark. Doesn't matter right now since // we only use this selector for determining ActionResultModal appearance return false; } const appState = getStateFromNavigatorRoute(currentRootSubroute); let appIndex = appState.index; let currentAppSubroute = appState.routes[appIndex]; while (currentAppSubroute.name === ActionResultModalRouteName) { currentAppSubroute = appState.routes[--appIndex]; } if (scrollBlockingModals.includes(currentAppSubroute.name)) { // All the scroll-blocking chat modals have a dark background return true; } return theme === 'dark'; } function activeThread( navigationState: ?PossiblyStaleNavigationState, validRouteNames: $ReadOnlyArray, ): ?string { if (!navigationState) { return null; } let rootIndex = navigationState.index; let currentRootSubroute = navigationState.routes[rootIndex]; while (currentRootSubroute.name !== AppRouteName) { if (!chatRootModals.includes(currentRootSubroute.name)) { return null; } if (rootIndex === 0) { return null; } currentRootSubroute = navigationState.routes[--rootIndex]; } const appState = getStateFromNavigatorRoute(currentRootSubroute); const [firstAppSubroute] = appState.routes; - if (firstAppSubroute.name !== TabNavigatorRouteName) { + if (firstAppSubroute.name !== CommunityDrawerNavigatorRouteName) { + return null; + } + const communityDrawerState = getStateFromNavigatorRoute(firstAppSubroute); + const [firstCommunityDrawerSubroute] = communityDrawerState.routes; + if (firstCommunityDrawerSubroute.name !== TabNavigatorRouteName) { return null; } - const tabState = getStateFromNavigatorRoute(firstAppSubroute); + const tabState = getStateFromNavigatorRoute(firstCommunityDrawerSubroute); const currentTabSubroute = tabState.routes[tabState.index]; if (currentTabSubroute.name !== ChatRouteName) { return null; } const chatState = getStateFromNavigatorRoute(currentTabSubroute); const currentChatSubroute = chatState.routes[chatState.index]; return getThreadIDFromRoute(currentChatSubroute, validRouteNames); } const activeThreadSelector: ( context: ?NavContextType, ) => ?string = createSelector( (context: ?NavContextType) => context && context.state, (navigationState: ?PossiblyStaleNavigationState): ?string => activeThread(navigationState, threadRoutes), ); const messageListRouteNames = [MessageListRouteName]; const activeMessageListSelector: ( context: ?NavContextType, ) => ?string = createSelector( (context: ?NavContextType) => context && context.state, (navigationState: ?PossiblyStaleNavigationState): ?string => activeThread(navigationState, messageListRouteNames), ); function useActiveThread(): ?string { const navContext = React.useContext(NavContext); return React.useMemo(() => { if (!navContext) { return null; } const { state } = navContext; return activeThread(state, threadRoutes); }, [navContext]); } function useActiveMessageList(): ?string { const navContext = React.useContext(NavContext); return React.useMemo(() => { if (!navContext) { return null; } const { state } = navContext; return activeThread(state, messageListRouteNames); }, [navContext]); } const calendarTabActiveSelector = createActiveTabSelector(CalendarRouteName); const threadPickerActiveSelector = createIsForegroundSelector( ThreadPickerModalRouteName, ); const calendarActiveSelector: ( context: ?NavContextType, ) => boolean = createSelector( calendarTabActiveSelector, threadPickerActiveSelector, (calendarTabActive: boolean, threadPickerActive: boolean) => calendarTabActive || threadPickerActive, ); const nativeCalendarQuery: ( input: NavPlusRedux, ) => () => CalendarQuery = createSelector( (input: NavPlusRedux) => currentCalendarQuery(input.redux), (input: NavPlusRedux) => calendarActiveSelector(input.navContext), ( calendarQuery: (calendarActive: boolean) => CalendarQuery, calendarActive: boolean, ) => () => calendarQuery(calendarActive), ); const nonThreadCalendarQuery: ( input: NavPlusRedux, ) => () => CalendarQuery = createSelector( nativeCalendarQuery, (input: NavPlusRedux) => nonThreadCalendarFiltersSelector(input.redux), ( calendarQuery: () => CalendarQuery, filters: $ReadOnlyArray, ) => { return (): CalendarQuery => { const query = calendarQuery(); return { startDate: query.startDate, endDate: query.endDate, filters, }; }; }, ); function useCalendarQuery(): () => CalendarQuery { const navContext = React.useContext(NavContext); return useSelector(state => nonThreadCalendarQuery({ redux: state, navContext, }), ); } export { createIsForegroundSelector, useIsAppLoggedIn, createActiveTabSelector, scrollBlockingModalsClosedSelector, selectBackgroundIsDark, activeThreadSelector, activeMessageListSelector, useActiveThread, useActiveMessageList, calendarActiveSelector, nativeCalendarQuery, nonThreadCalendarQuery, useCalendarQuery, }; diff --git a/native/navigation/route-names.js b/native/navigation/route-names.js index c5e8114af..8a3c80d4c 100644 --- a/native/navigation/route-names.js +++ b/native/navigation/route-names.js @@ -1,177 +1,181 @@ // @flow import type { RouteProp } from '@react-navigation/native'; import type { TermsAndPrivacyModalParams } from '../account/terms-and-privacy-modal.react'; import type { ThreadPickerModalParams } from '../calendar/thread-picker-modal.react'; import type { ComposeSubchannelParams } from '../chat/compose-subchannel.react'; import type { ImagePasteModalParams } from '../chat/image-paste-modal.react'; import type { MessageListParams } from '../chat/message-list-types'; import type { MultimediaMessageTooltipModalParams } from '../chat/multimedia-message-tooltip-modal.react'; import type { RobotextMessageTooltipModalParams } from '../chat/robotext-message-tooltip-modal.react'; import type { AddUsersModalParams } from '../chat/settings/add-users-modal.react'; import type { ColorSelectorModalParams } from '../chat/settings/color-selector-modal.react'; import type { ComposeSubchannelModalParams } from '../chat/settings/compose-subchannel-modal.react'; import type { DeleteThreadParams } from '../chat/settings/delete-thread.react'; import type { ThreadSettingsMemberTooltipModalParams } from '../chat/settings/thread-settings-member-tooltip-modal.react'; import type { ThreadSettingsParams } from '../chat/settings/thread-settings.react'; import type { SidebarListModalParams } from '../chat/sidebar-list-modal.react'; import type { TextMessageTooltipModalParams } from '../chat/text-message-tooltip-modal.react'; import type { CameraModalParams } from '../media/camera-modal.react'; import type { ImageModalParams } from '../media/image-modal.react'; import type { VideoPlaybackModalParams } from '../media/video-playback-modal.react'; import type { CustomServerModalParams } from '../profile/custom-server-modal.react'; import type { RelationshipListItemTooltipModalParams } from '../profile/relationship-list-item-tooltip-modal.react'; import type { ActionResultModalParams } from './action-result-modal.react'; export const ActionResultModalRouteName = 'ActionResultModal'; export const AddUsersModalRouteName = 'AddUsersModal'; export const AppearancePreferencesRouteName = 'AppearancePreferences'; export const AppRouteName = 'App'; export const AppsRouteName = 'Apps'; export const BackgroundChatThreadListRouteName = 'BackgroundChatThreadList'; export const BlockListRouteName = 'BlockList'; export const BuildInfoRouteName = 'BuildInfo'; export const CalendarRouteName = 'Calendar'; export const CameraModalRouteName = 'CameraModal'; export const ChatRouteName = 'Chat'; export const ChatThreadListRouteName = 'ChatThreadList'; export const ColorSelectorModalRouteName = 'ColorSelectorModal'; export const ComposeSubchannelModalRouteName = 'ComposeSubchannelModal'; export const ComposeSubchannelRouteName = 'ComposeSubchannel'; +export const CommunityDrawerNavigatorRouteName = 'CommunityDrawerNavigator'; export const CustomServerModalRouteName = 'CustomServerModal'; export const DefaultNotificationsPreferencesRouteName = 'DefaultNotifications'; export const DeleteAccountRouteName = 'DeleteAccount'; export const DeleteThreadRouteName = 'DeleteThread'; export const DevToolsRouteName = 'DevTools'; export const EditPasswordRouteName = 'EditPassword'; export const FriendListRouteName = 'FriendList'; export const HomeChatThreadListRouteName = 'HomeChatThreadList'; export const ImageModalRouteName = 'ImageModal'; export const ImagePasteModalRouteName = 'ImagePasteModal'; export const LoggedOutModalRouteName = 'LoggedOutModal'; export const MessageListRouteName = 'MessageList'; export const MultimediaMessageTooltipModalRouteName = 'MultimediaMessageTooltipModal'; export const PrivacyPreferencesRouteName = 'PrivacyPreferences'; export const ProfileRouteName = 'Profile'; export const ProfileScreenRouteName = 'ProfileScreen'; export const RelationshipListItemTooltipModalRouteName = 'RelationshipListItemTooltipModal'; export const RobotextMessageTooltipModalRouteName = 'RobotextMessageTooltipModal'; export const SidebarListModalRouteName = 'SidebarListModal'; export const TabNavigatorRouteName = 'TabNavigator'; export const TextMessageTooltipModalRouteName = 'TextMessageTooltipModal'; export const ThreadPickerModalRouteName = 'ThreadPickerModal'; export const ThreadSettingsMemberTooltipModalRouteName = 'ThreadSettingsMemberTooltipModal'; export const ThreadSettingsRouteName = 'ThreadSettings'; export const VideoPlaybackModalRouteName = 'VideoPlaybackModal'; export const TermsAndPrivacyRouteName = 'TermsAndPrivacyModal'; export type RootParamList = { +LoggedOutModal: void, +App: void, +ThreadPickerModal: ThreadPickerModalParams, +AddUsersModal: AddUsersModalParams, +CustomServerModal: CustomServerModalParams, +ColorSelectorModal: ColorSelectorModalParams, +ComposeSubchannelModal: ComposeSubchannelModalParams, +SidebarListModal: SidebarListModalParams, +ImagePasteModal: ImagePasteModalParams, +TermsAndPrivacyModal: TermsAndPrivacyModalParams, }; export type MessageTooltipRouteNames = | typeof RobotextMessageTooltipModalRouteName | typeof MultimediaMessageTooltipModalRouteName | typeof TextMessageTooltipModalRouteName; export type TooltipModalParamList = { +MultimediaMessageTooltipModal: MultimediaMessageTooltipModalParams, +TextMessageTooltipModal: TextMessageTooltipModalParams, +ThreadSettingsMemberTooltipModal: ThreadSettingsMemberTooltipModalParams, +RelationshipListItemTooltipModal: RelationshipListItemTooltipModalParams, +RobotextMessageTooltipModal: RobotextMessageTooltipModalParams, }; export type OverlayParamList = { - +TabNavigator: void, + +CommunityDrawerNavigator: void, +ImageModal: ImageModalParams, +ActionResultModal: ActionResultModalParams, +CameraModal: CameraModalParams, +VideoPlaybackModal: VideoPlaybackModalParams, ...TooltipModalParamList, }; export type TabParamList = { +Calendar: void, +Chat: void, +Profile: void, +Apps: void, }; export type ChatParamList = { +ChatThreadList: void, +MessageList: MessageListParams, +ComposeSubchannel: ComposeSubchannelParams, +ThreadSettings: ThreadSettingsParams, +DeleteThread: DeleteThreadParams, }; export type ChatTopTabsParamList = { +HomeChatThreadList: void, +BackgroundChatThreadList: void, }; export type ProfileParamList = { +ProfileScreen: void, +EditPassword: void, +DeleteAccount: void, +BuildInfo: void, +DevTools: void, +AppearancePreferences: void, +PrivacyPreferences: void, +DefaultNotifications: void, +FriendList: void, +BlockList: void, }; +export type CommunityDrawerParamList = { +TabNavigator: void }; + export type ScreenParamList = { ...RootParamList, ...OverlayParamList, ...TabParamList, ...ChatParamList, ...ChatTopTabsParamList, ...ProfileParamList, + ...CommunityDrawerParamList, }; export type NavigationRoute< RouteName: string = $Keys, > = RouteProp; export const accountModals = [LoggedOutModalRouteName]; export const scrollBlockingModals = [ ImageModalRouteName, MultimediaMessageTooltipModalRouteName, TextMessageTooltipModalRouteName, ThreadSettingsMemberTooltipModalRouteName, RelationshipListItemTooltipModalRouteName, RobotextMessageTooltipModalRouteName, VideoPlaybackModalRouteName, ]; export const chatRootModals = [ AddUsersModalRouteName, ColorSelectorModalRouteName, ComposeSubchannelModalRouteName, ]; export const threadRoutes = [ MessageListRouteName, ThreadSettingsRouteName, DeleteThreadRouteName, ComposeSubchannelRouteName, ]; diff --git a/native/navigation/tab-navigator.react.js b/native/navigation/tab-navigator.react.js index af8284126..0ba92067f 100644 --- a/native/navigation/tab-navigator.react.js +++ b/native/navigation/tab-navigator.react.js @@ -1,137 +1,138 @@ // @flow import type { BottomTabNavigationHelpers, BottomTabNavigationProp, } from '@react-navigation/bottom-tabs'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import * as React from 'react'; import { unreadCount } from 'lib/selectors/thread-selectors'; import AppsDirectory from '../apps/apps-directory.react'; import Calendar from '../calendar/calendar.react'; import Chat from '../chat/chat.react'; import SWMansionIcon from '../components/swmansion-icon.react'; import Profile from '../profile/profile.react'; import { useSelector } from '../redux/redux-utils'; import { useColors } from '../themes/colors'; +import type { CommunityDrawerNavigationProp } from './community-drawer-navigator.react'; import { CalendarRouteName, ChatRouteName, ProfileRouteName, AppsRouteName, type ScreenParamList, type TabParamList, } from './route-names'; import type { NavigationRoute } from './route-names'; import { tabBar } from './tab-bar.react'; const calendarTabOptions = { tabBarLabel: 'Calendar', // eslint-disable-next-line react/display-name tabBarIcon: ({ color }) => ( ), }; const getChatTabOptions = (badge: number) => ({ tabBarLabel: 'Inbox', // eslint-disable-next-line react/display-name tabBarIcon: ({ color }) => ( ), tabBarBadge: badge ? badge : undefined, }); const profileTabOptions = { tabBarLabel: 'Profile', // eslint-disable-next-line react/display-name tabBarIcon: ({ color }) => ( ), }; const appsTabOptions = { tabBarLabel: 'Apps', // eslint-disable-next-line react/display-name tabBarIcon: ({ color }) => ( ), }; export type TabNavigationProp< RouteName: $Keys = $Keys, > = BottomTabNavigationProp; const Tab = createBottomTabNavigator< ScreenParamList, TabParamList, BottomTabNavigationHelpers, >(); type Props = { - +navigation: TabNavigationProp<'TabNavigator'>, + +navigation: CommunityDrawerNavigationProp<'TabNavigator'>, +route: NavigationRoute<'TabNavigator'>, }; // eslint-disable-next-line no-unused-vars function TabNavigator(props: Props): React.Node { const colors = useColors(); const chatBadge = useSelector(unreadCount); const isCalendarEnabled = useSelector(state => state.enabledApps.calendar); let calendarTab; if (isCalendarEnabled) { calendarTab = ( ); } const tabBarScreenOptions = React.useMemo( () => ({ headerShown: false, tabBarHideOnKeyboard: false, tabBarActiveTintColor: colors.tabBarActiveTintColor, tabBarStyle: { backgroundColor: colors.tabBarBackground, borderTopWidth: 1, }, lazy: false, }), [colors.tabBarActiveTintColor, colors.tabBarBackground], ); return ( {calendarTab} ); } const styles = { icon: { fontSize: 28, }, }; export default TabNavigator;