Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wave8] Ideal Nav Left Hand Panel Search #33499

Merged
merged 4 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/NAVIGATORS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* */
export default {
CENTRAL_PANE_NAVIGATOR: 'CentralPaneNavigator',
LEFT_MODAL_NAVIGATOR: 'LeftModalNavigator',
RIGHT_MODAL_NAVIGATOR: 'RightModalNavigator',
FULL_SCREEN_NAVIGATOR: 'FullScreenNavigator',
} as const;
4 changes: 3 additions & 1 deletion src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,12 @@ const SCREENS = {
SAVE_THE_WORLD: {
ROOT: 'SaveTheWorld_Root',
},
LEFT_MODAL: {
SEARCH: 'Search',
},
RIGHT_MODAL: {
SETTINGS: 'Settings',
NEW_CHAT: 'NewChat',
SEARCH: 'Search',
DETAILS: 'Details',
PROFILE: 'Profile',
REPORT_DETAILS: 'Report_Details',
Expand Down
7 changes: 7 additions & 0 deletions src/libs/Navigation/AppNavigator/AuthScreens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import createCustomStackNavigator from './createCustomStackNavigator';
import defaultScreenOptions from './defaultScreenOptions';
import getRootNavigatorScreenOptions from './getRootNavigatorScreenOptions';
import CentralPaneNavigator from './Navigators/CentralPaneNavigator';
import LeftModalNavigator from './Navigators/LeftModalNavigator';
import RightModalNavigator from './Navigators/RightModalNavigator';

type AuthScreensProps = {
Expand Down Expand Up @@ -295,6 +296,12 @@ function AuthScreens({lastUpdateIDAppliedToClient, session, lastOpenedPublicRoom
component={RightModalNavigator}
listeners={modalScreenListeners}
/>
<RootStack.Screen
name={NAVIGATORS.LEFT_MODAL_NAVIGATOR}
options={screenOptions.leftModalNavigator}
component={LeftModalNavigator}
listeners={modalScreenListeners}
/>
<RootStack.Screen
name={SCREENS.DESKTOP_SIGN_IN_REDIRECT}
options={screenOptions.fullScreen}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import {CardStyleInterpolators, StackNavigationOptions} from '@react-navigation/
import {ThemeStyles} from '@styles/index';

/**
* RHP stack navigator screen options generator function
* Modal stack navigator screen options generator function
* @param themeStyles - The styles object
* @returns The screen options object
*/
const RHPScreenOptions = (themeStyles: ThemeStyles): StackNavigationOptions => ({
const ModalNavigatorScreenOptions = (themeStyles: ThemeStyles): StackNavigationOptions => ({
headerShown: false,
animationEnabled: true,
gestureDirection: 'horizontal',
cardStyle: themeStyles.navigationScreenCardStyle,
cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS,
});

export default RHPScreenOptions;
export default ModalNavigatorScreenOptions;
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {createStackNavigator, StackScreenProps} from '@react-navigation/stack';
import React, {useMemo} from 'react';
import {View} from 'react-native';
import NoDropZone from '@components/DragAndDrop/NoDropZone';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import ModalNavigatorScreenOptions from '@libs/Navigation/AppNavigator/ModalNavigatorScreenOptions';
import * as ModalStackNavigators from '@libs/Navigation/AppNavigator/ModalStackNavigators';
import {AuthScreensParamList, LeftModalNavigatorParamList} from '@libs/Navigation/types';
import NAVIGATORS from '@src/NAVIGATORS';
import SCREENS from '@src/SCREENS';
import Overlay from './Overlay';

type LeftModalNavigatorProps = StackScreenProps<AuthScreensParamList, typeof NAVIGATORS.LEFT_MODAL_NAVIGATOR>;

const Stack = createStackNavigator<LeftModalNavigatorParamList>();

function LeftModalNavigator({navigation}: LeftModalNavigatorProps) {
const styles = useThemeStyles();
const {isSmallScreenWidth} = useWindowDimensions();
const screenOptions = useMemo(() => ModalNavigatorScreenOptions(styles), [styles]);

return (
<NoDropZone>
{!isSmallScreenWidth && (
<Overlay
isModalOnTheLeft
onPress={navigation.goBack}
/>
)}
<View style={styles.LHPNavigatorContainer(isSmallScreenWidth)}>
<Stack.Navigator screenOptions={screenOptions}>
<Stack.Screen
name={SCREENS.LEFT_MODAL.SEARCH}
component={ModalStackNavigators.SearchModalStackNavigator}
/>
</Stack.Navigator>
</View>
</NoDropZone>
);
}

LeftModalNavigator.displayName = 'LeftModalNavigator';

export default LeftModalNavigator;
7 changes: 5 additions & 2 deletions src/libs/Navigation/AppNavigator/Navigators/Overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@ import CONST from '@src/CONST';
type OverlayProps = {
/* Callback to close the modal */
onPress: () => void;

/* Returns whether a modal is displayed on the left side of the screen. By default, the modal is displayed on the right */
isModalOnTheLeft?: boolean;
};

function Overlay({onPress}: OverlayProps) {
function Overlay({onPress, isModalOnTheLeft = false}: OverlayProps) {
const styles = useThemeStyles();
const {current} = useCardAnimation();
const {translate} = useLocalize();

return (
<Animated.View style={styles.overlayStyles(current)}>
<Animated.View style={styles.overlayStyles(current, isModalOnTheLeft)}>
<View style={[styles.flex1, styles.flexColumn]}>
{/* In the latest Electron version buttons can't be both clickable and draggable.
That's why we added this workaround. Because of two Pressable components on the desktop app
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {View} from 'react-native';
import NoDropZone from '@components/DragAndDrop/NoDropZone';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import ModalNavigatorScreenOptions from '@libs/Navigation/AppNavigator/ModalNavigatorScreenOptions';
import * as ModalStackNavigators from '@libs/Navigation/AppNavigator/ModalStackNavigators';
import RHPScreenOptions from '@libs/Navigation/AppNavigator/RHPScreenOptions';
import type {AuthScreensParamList, RightModalNavigatorParamList} from '@navigation/types';
import NAVIGATORS from '@src/NAVIGATORS';
import SCREENS from '@src/SCREENS';
Expand All @@ -18,7 +18,7 @@ const Stack = createStackNavigator<RightModalNavigatorParamList>();
function RightModalNavigator({navigation}: RightModalNavigatorProps) {
const styles = useThemeStyles();
const {isSmallScreenWidth} = useWindowDimensions();
const screenOptions = useMemo(() => RHPScreenOptions(styles), [styles]);
const screenOptions = useMemo(() => ModalNavigatorScreenOptions(styles), [styles]);

return (
<NoDropZone>
Expand All @@ -33,10 +33,6 @@ function RightModalNavigator({navigation}: RightModalNavigatorProps) {
name={SCREENS.RIGHT_MODAL.NEW_CHAT}
component={ModalStackNavigators.NewChatModalStackNavigator}
/>
<Stack.Screen
name={SCREENS.RIGHT_MODAL.SEARCH}
component={ModalStackNavigators.SearchModalStackNavigator}
/>
<Stack.Screen
name={SCREENS.RIGHT_MODAL.DETAILS}
component={ModalStackNavigators.DetailsModalStackNavigator}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const commonScreenOptions: StackNavigationOptions = {
animationTypeForReplace: 'push',
};

const SLIDE_LEFT_OUTPUT_RANGE_MULTIPLIER = -1;

export default (isSmallScreenWidth: boolean, themeStyles: ThemeStyles): ScreenOptions => ({
rightModalNavigator: {
...commonScreenOptions,
Expand All @@ -32,7 +34,23 @@ export default (isSmallScreenWidth: boolean, themeStyles: ThemeStyles): ScreenOp
right: 0,
},
},
leftModalNavigator: {
...commonScreenOptions,
cardStyleInterpolator: (props) => modalCardStyleInterpolator(isSmallScreenWidth, false, props, SLIDE_LEFT_OUTPUT_RANGE_MULTIPLIER),
presentation: 'transparentModal',

// We want pop in LHP since there are some flows that would work weird otherwise
animationTypeForReplace: 'pop',
cardStyle: {
...getNavigationModalCardStyle(),

// This is necessary to cover translated sidebar with overlay.
width: isSmallScreenWidth ? '100%' : '200%',

// LHP should be displayed in place of the sidebar
left: isSmallScreenWidth ? 0 : -variables.sideBarWidth,
},
},
homeScreen: {
title: CONFIG.SITE_TITLE,
...commonScreenOptions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@ import {Animated} from 'react-native';
import getCardStyles from '@styles/utils/cardStyles';
import variables from '@styles/variables';

export default (isSmallScreenWidth: boolean, isFullScreenModal: boolean, {current: {progress}, inverted, layouts: {screen}}: StackCardInterpolationProps): StackCardInterpolatedStyle => {
export default (
isSmallScreenWidth: boolean,
isFullScreenModal: boolean,
{current: {progress}, inverted, layouts: {screen}}: StackCardInterpolationProps,
outputRangeMultiplier = 1,
): StackCardInterpolatedStyle => {
const translateX = Animated.multiply(
progress.interpolate({
inputRange: [0, 1],
outputRange: [isSmallScreenWidth ? screen.width : variables.sideBarWidth, 0],
outputRange: [outputRangeMultiplier * (isSmallScreenWidth ? screen.width : variables.sideBarWidth), 0],
extrapolate: 'clamp',
}),
inverted,
Expand Down
57 changes: 10 additions & 47 deletions src/libs/Navigation/Navigation.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import {findFocusedRoute, getActionFromState} from '@react-navigation/core';
import {findFocusedRoute} from '@react-navigation/core';
import {CommonActions, EventArg, getPathFromState, NavigationContainerEventMap, NavigationState, PartialState, StackActions} from '@react-navigation/native';
import findLastIndex from 'lodash/findLastIndex';
import Log from '@libs/Log';
import CONST from '@src/CONST';
import NAVIGATORS from '@src/NAVIGATORS';
import ROUTES, {Route} from '@src/ROUTES';
import SCREENS, {PROTECTED_SCREENS} from '@src/SCREENS';
import getStateFromPath from './getStateFromPath';
import {PROTECTED_SCREENS} from '@src/SCREENS';
import originalDismissModal from './dismissModal';
import originalGetTopmostReportActionId from './getTopmostReportActionID';
import originalGetTopmostReportId from './getTopmostReportId';
import linkingConfig from './linkingConfig';
import linkTo from './linkTo';
import navigationRef from './navigationRef';
import {StackNavigationAction, StateOrRoute} from './types';
import {StateOrRoute} from './types';

let resolveNavigationIsReadyPromise: () => void;
const navigationIsReadyPromise = new Promise<void>((resolve) => {
Expand Down Expand Up @@ -44,6 +43,9 @@ const getTopmostReportId = (state = navigationRef.getState()) => originalGetTopm
// Re-exporting the getTopmostReportActionID here to fill in default value for state. The getTopmostReportActionID isn't defined in this file to avoid cyclic dependencies.
const getTopmostReportActionId = (state = navigationRef.getState()) => originalGetTopmostReportActionId(state);

// Re-exporting the dismissModal here to fill in default value for navigationRef. The dismissModal isn't defined in this file to avoid cyclic dependencies.
const dismissModal = (targetReportId = '', ref = navigationRef) => originalDismissModal(targetReportId, ref);

/** Method for finding on which index in stack we are. */
function getActiveRouteIndex(stateOrRoute: StateOrRoute, index?: number): number | undefined {
if ('routes' in stateOrRoute && stateOrRoute.routes) {
Expand All @@ -56,7 +58,7 @@ function getActiveRouteIndex(stateOrRoute: StateOrRoute, index?: number): number
return getActiveRouteIndex(childActiveRoute, stateOrRoute.state.index ?? 0);
}

if ('name' in stateOrRoute && stateOrRoute.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR) {
if ('name' in stateOrRoute && (stateOrRoute.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR || stateOrRoute.name === NAVIGATORS.LEFT_MODAL_NAVIGATOR)) {
return 0;
}

Expand Down Expand Up @@ -160,8 +162,8 @@ function goBack(fallbackRoute: Route, shouldEnforceFallback = false, shouldPopTo
if (isFirstRouteInNavigator) {
const rootState = navigationRef.getRootState();
const lastRoute = rootState.routes.at(-1);
// If the user comes from a different flow (there is more than one route in RHP) we should go back to the previous flow on UP button press instead of using the fallbackRoute.
if (lastRoute?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR && (lastRoute.state?.index ?? 0) > 0) {
// If the user comes from a different flow (there is more than one route in ModalNavigator) we should go back to the previous flow on UP button press instead of using the fallbackRoute.
if ((lastRoute?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR || lastRoute?.name === NAVIGATORS.LEFT_MODAL_NAVIGATOR) && (lastRoute.state?.index ?? 0) > 0) {
navigationRef.current.goBack();
return;
}
Expand Down Expand Up @@ -200,45 +202,6 @@ function setParams(params: Record<string, unknown>, routeKey: string) {
});
}

/**
* Dismisses the last modal stack if there is any
*
* @param targetReportID - The reportID to navigate to after dismissing the modal
*/
function dismissModal(targetReportID?: string) {
if (!canNavigate('dismissModal')) {
return;
}
const rootState = navigationRef.getRootState();
const lastRoute = rootState.routes.at(-1);
switch (lastRoute?.name) {
case NAVIGATORS.RIGHT_MODAL_NAVIGATOR:
case SCREENS.NOT_FOUND:
case SCREENS.REPORT_ATTACHMENTS:
// if we are not in the target report, we need to navigate to it after dismissing the modal
if (targetReportID && targetReportID !== getTopmostReportId(rootState)) {
const state = getStateFromPath(ROUTES.REPORT_WITH_ID.getRoute(targetReportID));

const action: StackNavigationAction = getActionFromState(state, linkingConfig.config);
if (action) {
action.type = 'REPLACE';
navigationRef.current?.dispatch(action);
}
// If not-found page is in the route stack, we need to close it
} else if (targetReportID && rootState.routes.some((route) => route.name === SCREENS.NOT_FOUND)) {
const lastRouteIndex = rootState.routes.length - 1;
const centralRouteIndex = findLastIndex(rootState.routes, (route) => route.name === NAVIGATORS.CENTRAL_PANE_NAVIGATOR);
navigationRef.current?.dispatch({...StackActions.pop(lastRouteIndex - centralRouteIndex), target: rootState.key});
} else {
navigationRef.current?.dispatch({...StackActions.pop(), target: rootState.key});
}
break;
default: {
Log.hmmm('[Navigation] dismissModal failed because there is no modal stack to dismiss');
}
}
}

/**
* Returns the current active route without the URL params
*/
Expand Down
56 changes: 56 additions & 0 deletions src/libs/Navigation/dismissModal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {getActionFromState} from '@react-navigation/core';
import {NavigationContainerRef, StackActions} from '@react-navigation/native';
import {findLastIndex} from 'lodash';
import Log from '@libs/Log';
import NAVIGATORS from '@src/NAVIGATORS';
import ROUTES from '@src/ROUTES';
import SCREENS from '@src/SCREENS';
import getStateFromPath from './getStateFromPath';
import getTopmostReportId from './getTopmostReportId';
import linkingConfig from './linkingConfig';
import {RootStackParamList, StackNavigationAction} from './types';

// This function is in a separate file than Navigation.js to avoid cyclic dependency.

/**
* Dismisses the last modal stack if there is any
*
* @param targetReportID - The reportID to navigate to after dismissing the modal
*/
function dismissModal(targetReportID: string, navigationRef: NavigationContainerRef<RootStackParamList>) {
if (!navigationRef.isReady()) {
return;
}

const state = navigationRef.getState();
const lastRoute = state.routes.at(-1);
switch (lastRoute?.name) {
case NAVIGATORS.LEFT_MODAL_NAVIGATOR:
case NAVIGATORS.RIGHT_MODAL_NAVIGATOR:
case SCREENS.NOT_FOUND:
case SCREENS.REPORT_ATTACHMENTS:
// if we are not in the target report, we need to navigate to it after dismissing the modal
if (targetReportID && targetReportID !== getTopmostReportId(state)) {
const reportState = getStateFromPath(ROUTES.REPORT_WITH_ID.getRoute(targetReportID));

const action: StackNavigationAction = getActionFromState(reportState, linkingConfig.config);
if (action) {
action.type = 'REPLACE';
navigationRef.dispatch(action);
}
// If not-found page is in the route stack, we need to close it
} else if (targetReportID && state.routes.some((route) => route.name === SCREENS.NOT_FOUND)) {
const lastRouteIndex = state.routes.length - 1;
const centralRouteIndex = findLastIndex(state.routes, (route) => route.name === NAVIGATORS.CENTRAL_PANE_NAVIGATOR);
navigationRef.dispatch({...StackActions.pop(lastRouteIndex - centralRouteIndex), target: state.key});
} else {
navigationRef.dispatch({...StackActions.pop(), target: state.key});
}
break;
default: {
Log.hmmm('[Navigation] dismissModal failed because there is no modal stack to dismiss');
}
}
}

export default dismissModal;
Loading
Loading