diff --git a/native/navigation/community-drawer-navigator.react.js b/native/navigation/community-drawer-navigator.react.js
index 5cf1a21b6..d7e87ada6 100644
--- a/native/navigation/community-drawer-navigator.react.js
+++ b/native/navigation/community-drawer-navigator.react.js
@@ -1,72 +1,81 @@
// @flow
import {
createDrawerNavigator,
type DrawerNavigationHelpers,
type DrawerNavigationProp,
} from '@react-navigation/drawer';
import * as React from 'react';
import { View } from 'react-native';
import { useStyles } from '../themes/colors';
import type { AppNavigationProp } from './app-navigator.react';
import CommunityDrawerContent from './community-drawer-content.react';
+import { drawerSwipeEnabledSelector } from './nav-selectors';
+import { NavContext } from './navigation-context';
import { TabNavigatorRouteName } from './route-names';
import type {
NavigationRoute,
ScreenParamList,
CommunityDrawerParamList,
} from './route-names';
import TabNavigator from './tab-navigator.react';
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 navContext = React.useContext(NavContext);
+ const swipeEnabled = React.useMemo(
+ () => drawerSwipeEnabledSelector(navContext),
+ [navContext],
+ );
+
const screenOptions = React.useMemo(
() => ({
drawerStyle: styles.drawerStyle,
headerShown: false,
+ swipeEnabled,
}),
- [styles.drawerStyle],
+ [styles.drawerStyle, swipeEnabled],
);
return (
);
}
const unboundStyles = {
drawerView: {
flex: 1,
},
drawerStyle: {
width: '80%',
},
};
export { CommunityDrawerNavigator };
diff --git a/native/navigation/nav-selectors.js b/native/navigation/nav-selectors.js
index da095677c..22ec189f7 100644
--- a/native/navigation/nav-selectors.js
+++ b/native/navigation/nav-selectors.js
@@ -1,289 +1,338 @@
// @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 !== CommunityDrawerNavigatorRouteName) {
return false;
}
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 !== CommunityDrawerNavigatorRouteName) {
return null;
}
const communityDrawerState = getStateFromNavigatorRoute(firstAppSubroute);
const [firstCommunityDrawerSubroute] = communityDrawerState.routes;
if (firstCommunityDrawerSubroute.name !== TabNavigatorRouteName) {
return null;
}
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,
}),
);
}
+const drawerSwipeEnabledSelector: (
+ context: ?NavContextType,
+) => boolean = createSelector(
+ (context: ?NavContextType) => context && context.state,
+ (navigationState: ?PossiblyStaleNavigationState) => {
+ if (!navigationState) {
+ return true;
+ }
+
+ // First, we recurse into the navigation state until we find the tab route
+ // The tab route should always be accessible by recursing through the first
+ // routes of each subsequent nested navigation state
+ const [firstRootSubroute] = navigationState.routes;
+ if (firstRootSubroute.name !== AppRouteName) {
+ return true;
+ }
+ const appState = getStateFromNavigatorRoute(firstRootSubroute);
+ const [firstAppSubroute] = appState.routes;
+ if (firstAppSubroute.name !== CommunityDrawerNavigatorRouteName) {
+ return true;
+ }
+ const communityDrawerState = getStateFromNavigatorRoute(firstAppSubroute);
+ const [firstCommunityDrawerSubroute] = communityDrawerState.routes;
+ if (firstCommunityDrawerSubroute.name !== TabNavigatorRouteName) {
+ return true;
+ }
+ const tabState = getStateFromNavigatorRoute(firstCommunityDrawerSubroute);
+
+ // Once we have the tab state, we want to figure out if we currently have
+ // an active StackNavigator
+ const currentTabSubroute = tabState.routes[tabState.index];
+ if (!currentTabSubroute.state) {
+ return true;
+ }
+ const currentTabSubrouteState = getStateFromNavigatorRoute(
+ currentTabSubroute,
+ );
+ if (currentTabSubrouteState.type !== 'stack') {
+ return true;
+ }
+
+ // Finally, we want to disable the swipe gesture if there is a stack with
+ // more than one subroute, since then the stack will have its own swipe
+ // gesture that will conflict with the drawer's
+ return currentTabSubrouteState.routes.length < 2;
+ },
+);
+
export {
createIsForegroundSelector,
useIsAppLoggedIn,
createActiveTabSelector,
scrollBlockingModalsClosedSelector,
selectBackgroundIsDark,
activeThreadSelector,
activeMessageListSelector,
useActiveThread,
useActiveMessageList,
calendarActiveSelector,
nativeCalendarQuery,
nonThreadCalendarQuery,
useCalendarQuery,
+ drawerSwipeEnabledSelector,
};