diff --git a/patches/@react-navigation+stack+6.3.16+002+dontDetachScreen.patch b/patches/@react-navigation+stack+6.3.16+002+dontDetachScreen.patch index d64fc4fecf74..ac4e8bafa8ab 100644 --- a/patches/@react-navigation+stack+6.3.16+002+dontDetachScreen.patch +++ b/patches/@react-navigation+stack+6.3.16+002+dontDetachScreen.patch @@ -43,7 +43,7 @@ index 7558eb3..b7bb75e 100644 }) : STATE_TRANSITIONING_OR_BELOW_TOP; } + -+ const isHomeScreenAndNotOnTop = route.name === 'Home' && isScreenActive !== STATE_ON_TOP; ++ const isHomeScreenAndNotOnTop = (route.name === 'Home' || route.name === 'SettingsHome') && isScreenActive !== STATE_ON_TOP; + const { headerShown = true, diff --git a/src/CONST.ts b/src/CONST.ts index 072f780b54ae..475bd5cd5baf 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -961,6 +961,7 @@ const CONST = { 3: 100, }, }, + CENTRAL_PANE_ANIMATION_HEIGHT: 200, LHN_SKELETON_VIEW_ITEM_HEIGHT: 64, EXPENSIFY_PARTNER_NAME: 'expensify.com', EMAIL: { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 425ff73af56b..25e29252c390 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -57,6 +57,8 @@ const ROUTES = { }, SETTINGS: 'settings', + SETTINGS_HOME: 'settings_new', + SETTINGS_NEW_PROFILE: 'settings_new/profile', SETTINGS_PROFILE: 'settings/profile', SETTINGS_SHARE_CODE: 'settings/shareCode', SETTINGS_DISPLAY_NAME: 'settings/profile/display-name', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 921f57953482..72add301bec6 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -17,6 +17,8 @@ const SCREENS = { TRANSITION_BETWEEN_APPS: 'TransitionBetweenApps', VALIDATE_LOGIN: 'ValidateLogin', UNLINK_LOGIN: 'UnlinkLogin', + SETTINGS_HOME: 'SettingsHome', + SETTINGS_CENTRAL_PANE: 'SettingsCentralPane', SETTINGS: { ROOT: 'Settings_Root', SHARE_CODE: 'Settings_Share_Code', diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index 9b061ba5c670..11d0e4e7b45c 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -94,6 +94,9 @@ const propTypes = { horizontal: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL)), vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)), }), + + /** Style applied to the avatar */ + avatarStyle: stylePropTypes.isRequired, }; const defaultProps = { @@ -141,6 +144,7 @@ function AvatarWithImagePicker({ anchorAlignment, onImageSelected, editorMaskImage, + avatarStyle, }) { const theme = useTheme(); const styles = useThemeStyles(); @@ -287,7 +291,7 @@ function AvatarWithImagePicker({ return ( - + {source ? ( - - {shouldShowBackButton && ( + + {shouldFinalShowBackButton && ( { diff --git a/src/components/IllustratedHeaderPageLayout.js b/src/components/IllustratedHeaderPageLayout.js index 0557c7a6ca7b..bf2d39e6adfd 100644 --- a/src/components/IllustratedHeaderPageLayout.js +++ b/src/components/IllustratedHeaderPageLayout.js @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import React from 'react'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import useTheme from '@styles/themes/useTheme'; import useThemeStyles from '@styles/useThemeStyles'; import HeaderPageLayout from './HeaderPageLayout'; @@ -34,6 +35,10 @@ const defaultProps = { function IllustratedHeaderPageLayout({backgroundColor, children, illustration, footer, overlayContent, ...propsToPassToHeader}) { const theme = useTheme(); const styles = useThemeStyles(); + const {isSmallScreenWidth} = useWindowDimensions(); + + const shouldUseMaxHeight = propsToPassToHeader.shouldUseCentralPaneView && !isSmallScreenWidth; + return ( {overlayContent && overlayContent()} } - headerContainerStyles={[styles.justifyContentCenter, styles.w100]} + // TODO: move to variables + headerContainerStyles={[styles.justifyContentCenter, styles.w100, shouldUseMaxHeight && styles.centralPaneAnimation]} footer={footer} // eslint-disable-next-line react/jsx-props-no-spreading {...propsToPassToHeader} diff --git a/src/components/QRShare/index.js b/src/components/QRShare/index.js index f7bb5cb5f788..3472a31a1098 100644 --- a/src/components/QRShare/index.js +++ b/src/components/QRShare/index.js @@ -4,14 +4,17 @@ import _ from 'underscore'; import ExpensifyWordmark from '@assets/images/expensify-wordmark.svg'; import QRCode from '@components/QRCode'; import Text from '@components/Text'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import useTheme from '@styles/themes/useTheme'; import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; +import CONST from '@src/CONST'; import {qrShareDefaultProps, qrSharePropTypes} from './propTypes'; function QRShare({innerRef, url, title, subtitle, logo, logoRatio, logoMarginRatio}) { const styles = useThemeStyles(); const theme = useTheme(); + const {isSmallScreenWidth} = useWindowDimensions(); const [qrCodeSize, setQrCodeSize] = useState(1); const svgRef = useRef(null); @@ -26,7 +29,11 @@ function QRShare({innerRef, url, title, subtitle, logo, logoRatio, logoMarginRat const onLayout = (event) => { const containerWidth = event.nativeEvent.layout.width - variables.qrShareHorizontalPadding * 2 || 0; - setQrCodeSize(Math.max(1, containerWidth)); + if (isSmallScreenWidth) { + setQrCodeSize(Math.max(1, containerWidth)); + return; + } + setQrCodeSize(Math.max(1, Math.min(containerWidth, CONST.CENTRAL_PANE_ANIMATION_HEIGHT))); }; return ( diff --git a/src/languages/en.ts b/src/languages/en.ts index c4a481cb71c0..fa4263b5f43e 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -758,6 +758,9 @@ export default { phrase4: 'Privacy', }, help: 'Help', + accountSettings: 'Account Settings', + account: 'Account', + general: 'General', }, closeAccountPage: { closeAccount: 'Close account', diff --git a/src/languages/es.ts b/src/languages/es.ts index a91a8768a3ee..f6fd17a3ac97 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -753,6 +753,9 @@ export default { phrase4: 'Privacidad', }, help: 'Ayuda', + accountSettings: 'ConfiguraciĆ³n de la cuenta', + account: 'Cuenta', + general: 'General', }, closeAccountPage: { closeAccount: 'Cerrar cuenta', diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 008015db7a90..7a057aaacbd4 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -5,11 +5,11 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import KeyboardShortcut from '@libs/KeyboardShortcut'; import getCurrentUrl from '@libs/Navigation/currentUrl'; import Navigation from '@libs/Navigation/Navigation'; +import {AuthScreensParamList} from '@libs/Navigation/types'; import NetworkConnection from '@libs/NetworkConnection'; import * as Pusher from '@libs/Pusher/pusher'; import PusherConnectionManager from '@libs/PusherConnectionManager'; import * as SessionUtils from '@libs/SessionUtils'; -import type {AuthScreensParamList} from '@navigation/types'; import DemoSetupPage from '@pages/DemoSetupPage'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import DesktopSignInRedirectPage from '@pages/signin/DesktopSignInRedirectPage'; @@ -36,6 +36,7 @@ import createCustomStackNavigator from './createCustomStackNavigator'; import defaultScreenOptions from './defaultScreenOptions'; import getRootNavigatorScreenOptions from './getRootNavigatorScreenOptions'; import CentralPaneNavigator from './Navigators/CentralPaneNavigator'; +import FullScreenNavigator from './Navigators/FullScreenNavigator'; import RightModalNavigator from './Navigators/RightModalNavigator'; type AuthScreensProps = { @@ -318,6 +319,11 @@ function AuthScreens({lastUpdateIDAppliedToClient, session, lastOpenedPublicRoom component={RightModalNavigator} listeners={modalScreenListeners} /> + React.ComponentType>>; * * @param screens key/value pairs where the key is the name of the screen and the value is a functon that returns the lazy-loaded component */ -function createModalStackNavigator(screens: Screens): React.ComponentType { +function createModalStackNavigator(screens: Screens, getScreenOptions?: (styles: ThemeStyles) => StackNavigationOptions): React.ComponentType { const ModalStackNavigator = createStackNavigator(); function ModalStack() { @@ -55,7 +56,7 @@ function createModalStackNavigator(screens: ); return ( - + {Object.keys(screens as Required).map((name) => ( require('../../../pages/TeachersUnite/ImTeacherPage').default as React.ComponentType, }); +// should it be merged with SettingsModalStackNavigator? +const AccountSettingsModalStackNavigator = createModalStackNavigator( + { + [SCREENS.SETTINGS.WORKSPACES]: () => require('../../../pages/workspace/WorkspacesListPage').default as React.ComponentType, + [SCREENS.SETTINGS.PREFERENCES.ROOT]: () => require('../../../pages/settings/Preferences/PreferencesPage').default as React.ComponentType, + [SCREENS.SETTINGS.SECURITY]: () => require('../../../pages/settings/Security/SecuritySettingsPage').default as React.ComponentType, + [SCREENS.SETTINGS.PROFILE.ROOT]: () => require('../../../pages/settings/Profile/ProfilePage').default as React.ComponentType, + [SCREENS.SETTINGS.SHARE_CODE]: () => require('../../../pages/ShareCodePage').default as React.ComponentType, + [SCREENS.SETTINGS.WALLET.ROOT]: () => require('../../../pages/settings/Wallet/WalletPage').default as React.ComponentType, + [SCREENS.SETTINGS.ABOUT]: () => require('../../../pages/settings/AboutPage/AboutPage').default as React.ComponentType, + }, + (styles) => ({cardStyle: styles.navigationScreenCardStyle, headerShown: false}), +); + const SettingsModalStackNavigator = createModalStackNavigator({ [SCREENS.SETTINGS.ROOT]: () => require('../../../pages/settings/InitialSettingsPage').default as React.ComponentType, [SCREENS.SETTINGS.SHARE_CODE]: () => require('../../../pages/ShareCodePage').default as React.ComponentType, @@ -291,6 +306,7 @@ export { SearchModalStackNavigator, NewChatModalStackNavigator, NewTaskModalStackNavigator, + AccountSettingsModalStackNavigator, SettingsModalStackNavigator, EnablePaymentsStackNavigator, AddPersonalBankAccountModalStackNavigator, diff --git a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx new file mode 100644 index 000000000000..4c65bfa3ae71 --- /dev/null +++ b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import {View} from 'react-native'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import createCustomFullScreenNavigator from '@libs/Navigation/AppNavigator/createCustomFullScreenNavigator'; +import getRootNavigatorScreenOptions from '@libs/Navigation/AppNavigator/getRootNavigatorScreenOptions'; +import * as ModalStackNavigators from '@libs/Navigation/AppNavigator/ModalStackNavigators'; +import useThemeStyles from '@styles/useThemeStyles'; +import SCREENS from '@src/SCREENS'; + +const loadPage = () => require('../../../../pages/settings/InitialSettingsPage').default as React.ComponentType; + +const RootStack = createCustomFullScreenNavigator(); + +function FullScreenNavigator() { + const styles = useThemeStyles(); + const {isSmallScreenWidth} = useWindowDimensions(); + const screenOptions = getRootNavigatorScreenOptions(isSmallScreenWidth, styles); + + return ( + + + + + + + ); +} + +FullScreenNavigator.displayName = 'FullScreenNavigator'; + +export default FullScreenNavigator; diff --git a/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/CustomFullScreenRouter.tsx b/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/CustomFullScreenRouter.tsx new file mode 100644 index 000000000000..fbd62595f5b6 --- /dev/null +++ b/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/CustomFullScreenRouter.tsx @@ -0,0 +1,60 @@ +import {NavigationState, ParamListBase, PartialState, RouterConfigOptions, StackNavigationState, StackRouter} from '@react-navigation/native'; +import getIsSmallScreenWidth from '@libs/getIsSmallScreenWidth'; +import SCREENS from '@src/SCREENS'; +import {ResponsiveStackNavigatorRouterOptions} from './types'; + +// TODO: export states to separate file +type State = NavigationState | PartialState; + +const isAtLeastOneInState = (state: State, screenName: string): boolean => !!state.routes.find((route) => route.name === screenName); + +/** + * Adds report route without any specific reportID to the state. + * The report screen will self set proper reportID param based on the helper function findLastAccessedReport (look at ReportScreenWrapper for more info) + */ +const addCentralPaneNavigatorRoute = (state: State) => { + const centralPaneNavigatorRoute = { + name: SCREENS.SETTINGS_CENTRAL_PANE, + state: { + routes: [ + { + name: SCREENS.SETTINGS.PROFILE.ROOT, + }, + ], + }, + }; + state.routes.splice(1, 0, centralPaneNavigatorRoute); + // eslint-disable-next-line no-param-reassign, @typescript-eslint/non-nullable-type-assertion-style + (state.index as number) = state.routes.length - 1; +}; + +function CustomFullScreenRouter(options: ResponsiveStackNavigatorRouterOptions) { + const stackRouter = StackRouter(options); + + return { + ...stackRouter, + getInitialState({routeNames, routeParamList, routeGetIdList}: RouterConfigOptions) { + const isSmallScreenWidth = getIsSmallScreenWidth(); + const initialState = stackRouter.getInitialState({routeNames, routeParamList, routeGetIdList}); + if (!isAtLeastOneInState(initialState, SCREENS.SETTINGS_CENTRAL_PANE) && !isSmallScreenWidth) { + addCentralPaneNavigatorRoute(initialState); + } + return initialState; + }, + getRehydratedState(partialState: StackNavigationState, {routeNames, routeParamList, routeGetIdList}: RouterConfigOptions): StackNavigationState { + const isSmallScreenWidth = getIsSmallScreenWidth(); + // Make sure that there is at least one CentralPaneNavigator (ReportScreen by default) in the state if this is a wide layout + if (!isAtLeastOneInState(partialState, SCREENS.SETTINGS_CENTRAL_PANE) && !isSmallScreenWidth) { + // If we added a route we need to make sure that the state.stale is true to generate new key for this route + + // eslint-disable-next-line no-param-reassign + (partialState.stale as boolean) = true; + addCentralPaneNavigatorRoute(partialState); + } + const state = stackRouter.getRehydratedState(partialState, {routeNames, routeParamList, routeGetIdList}); + return state; + }, + }; +} + +export default CustomFullScreenRouter; diff --git a/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/index.native.tsx b/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/index.native.tsx new file mode 100644 index 000000000000..19129cd4826c --- /dev/null +++ b/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/index.native.tsx @@ -0,0 +1,42 @@ +import {createNavigatorFactory, ParamListBase, StackActionHelpers, StackNavigationState, useNavigationBuilder} from '@react-navigation/native'; +import {StackNavigationEventMap, StackNavigationOptions, StackView} from '@react-navigation/stack'; +import React, {useRef} from 'react'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import CustomFullScreenRouter from './CustomFullScreenRouter'; +import type {ResponsiveStackNavigatorProps, ResponsiveStackNavigatorRouterOptions} from './types'; + +function ResponsiveStackNavigator(props: ResponsiveStackNavigatorProps) { + const {isSmallScreenWidth} = useWindowDimensions(); + + const isSmallScreenWidthRef = useRef(isSmallScreenWidth); + + isSmallScreenWidthRef.current = isSmallScreenWidth; + + const {navigation, state, descriptors, NavigationContent} = useNavigationBuilder< + StackNavigationState, + ResponsiveStackNavigatorRouterOptions, + StackActionHelpers, + StackNavigationOptions, + StackNavigationEventMap + >(CustomFullScreenRouter, { + children: props.children, + screenOptions: props.screenOptions, + initialRouteName: props.initialRouteName, + }); + + return ( + + + + ); +} + +ResponsiveStackNavigator.displayName = 'ResponsiveStackNavigator'; + +export default createNavigatorFactory, StackNavigationOptions, StackNavigationEventMap, typeof ResponsiveStackNavigator>(ResponsiveStackNavigator); diff --git a/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/index.tsx b/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/index.tsx new file mode 100644 index 000000000000..782a5819f490 --- /dev/null +++ b/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/index.tsx @@ -0,0 +1,75 @@ +import {createNavigatorFactory, ParamListBase, StackActionHelpers, StackNavigationState, useNavigationBuilder} from '@react-navigation/native'; +import {StackNavigationEventMap, StackNavigationOptions, StackView} from '@react-navigation/stack'; +import React, {useMemo, useRef} from 'react'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import SCREENS from '@src/SCREENS'; +import CustomFullScreenRouter from './CustomFullScreenRouter'; +import type {ResponsiveStackNavigatorProps, ResponsiveStackNavigatorRouterOptions} from './types'; + +// TODO: Extract to utils with ./createCustomStackNavigator/index.tsx +type Routes = StackNavigationState['routes']; +function reduceReportRoutes(routes: Routes): Routes { + const result: Routes = []; + let count = 0; + const reverseRoutes = [...routes].reverse(); + + reverseRoutes.forEach((route) => { + if (route.name === SCREENS.SETTINGS_CENTRAL_PANE) { + // Remove all report routes except the last 3. This will improve performance. + if (count < 3) { + result.push(route); + count++; + } + } else { + result.push(route); + } + }); + + return result.reverse(); +} + +function ResponsiveStackNavigator(props: ResponsiveStackNavigatorProps) { + const {isSmallScreenWidth} = useWindowDimensions(); + + const isSmallScreenWidthRef = useRef(isSmallScreenWidth); + + isSmallScreenWidthRef.current = isSmallScreenWidth; + + const {navigation, state, descriptors, NavigationContent} = useNavigationBuilder< + StackNavigationState, + ResponsiveStackNavigatorRouterOptions, + StackActionHelpers, + StackNavigationOptions, + StackNavigationEventMap + >(CustomFullScreenRouter, { + children: props.children, + screenOptions: props.screenOptions, + initialRouteName: props.initialRouteName, + }); + + const stateToRender = useMemo(() => { + const result = reduceReportRoutes(state.routes); + + return { + ...state, + index: result.length - 1, + routes: [...result], + }; + }, [state]); + + return ( + + + + ); +} + +ResponsiveStackNavigator.displayName = 'ResponsiveStackNavigator'; + +export default createNavigatorFactory, StackNavigationOptions, StackNavigationEventMap, typeof ResponsiveStackNavigator>(ResponsiveStackNavigator); diff --git a/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/types.ts b/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/types.ts new file mode 100644 index 000000000000..707a0ff4498d --- /dev/null +++ b/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/types.ts @@ -0,0 +1,13 @@ +import {DefaultNavigatorOptions, ParamListBase, StackNavigationState, StackRouterOptions} from '@react-navigation/native'; +import {StackNavigationEventMap, StackNavigationOptions} from '@react-navigation/stack'; + +type ResponsiveStackNavigatorConfig = { + isSmallScreenWidth: boolean; +}; + +type ResponsiveStackNavigatorRouterOptions = StackRouterOptions; + +type ResponsiveStackNavigatorProps = DefaultNavigatorOptions, StackNavigationOptions, StackNavigationEventMap> & + ResponsiveStackNavigatorConfig; + +export type {ResponsiveStackNavigatorRouterOptions, ResponsiveStackNavigatorProps, ResponsiveStackNavigatorConfig}; diff --git a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts index 435ebc00362b..738cd8ec9de3 100644 --- a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts +++ b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts @@ -7,7 +7,7 @@ import type {ResponsiveStackNavigatorRouterOptions} from './types'; type State = NavigationState | PartialState; -const isAtLeastOneCentralPaneNavigatorInState = (state: State): boolean => !!state.routes.find((route) => route.name === NAVIGATORS.CENTRAL_PANE_NAVIGATOR); +const isAtLeastOneInState = (state: State, screenName: string): boolean => !!state.routes.find((route) => route.name === screenName); const getTopMostReportIDFromRHP = (state: State): string => { if (!state) { @@ -58,6 +58,60 @@ const addCentralPaneNavigatorRoute = (state: State) => { (state.index as number) = state.routes.length - 1; }; +const mapScreenNameToSettingsScreenName: Record = { + // [SCREENS.SETTINGS.PROFILE.CONTACT_METHODS]: SCREENS.SETTINGS.PROFILE, +}; + +const handleSettingsOpened = (state: State) => { + const rhpNav = state.routes.find((route) => route.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR); + if (!rhpNav?.state?.routes[0]) { + return; + } + const screen = rhpNav.state.routes[0]; + // check if we are on settings screen + if (screen.name !== 'Settings') { + return; + } + // check if we already have settings home route + if (isAtLeastOneInState(state, NAVIGATORS.FULL_SCREEN_NAVIGATOR)) { + return; + } + + const settingsScreenName = screen?.state?.routes[0].name; + + if (!settingsScreenName) { + return; + } + + const settingsHomeRouteName = mapScreenNameToSettingsScreenName[settingsScreenName] || SCREENS.SETTINGS.PROFILE.ROOT; + + const fullScreenRoute = { + name: NAVIGATORS.FULL_SCREEN_NAVIGATOR, + state: { + routes: [ + { + name: SCREENS.SETTINGS_HOME, + }, + { + name: SCREENS.SETTINGS_CENTRAL_PANE, + state: { + routes: [ + { + name: settingsHomeRouteName, + }, + ], + }, + }, + ], + }, + }; + state.routes.splice(2, 0, fullScreenRoute); + // eslint-disable-next-line no-param-reassign, @typescript-eslint/non-nullable-type-assertion-style + (state.index as number) = state.routes.length - 1; + // eslint-disable-next-line no-param-reassign, @typescript-eslint/non-nullable-type-assertion-style + (state.stale as boolean) = true; +}; + function CustomRouter(options: ResponsiveStackNavigatorRouterOptions) { const stackRouter = StackRouter(options); @@ -66,13 +120,14 @@ function CustomRouter(options: ResponsiveStackNavigatorRouterOptions) { getRehydratedState(partialState: StackNavigationState, {routeNames, routeParamList, routeGetIdList}: RouterConfigOptions): StackNavigationState { const isSmallScreenWidth = getIsSmallScreenWidth(); // Make sure that there is at least one CentralPaneNavigator (ReportScreen by default) in the state if this is a wide layout - if (!isAtLeastOneCentralPaneNavigatorInState(partialState) && !isSmallScreenWidth) { + if (!isAtLeastOneInState(partialState, NAVIGATORS.CENTRAL_PANE_NAVIGATOR) && !isSmallScreenWidth) { // If we added a route we need to make sure that the state.stale is true to generate new key for this route // eslint-disable-next-line no-param-reassign (partialState.stale as boolean) = true; addCentralPaneNavigatorRoute(partialState); } + handleSettingsOpened(partialState); const state = stackRouter.getRehydratedState(partialState, {routeNames, routeParamList, routeGetIdList}); return state; }, diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index c2dd3e76e7ad..b05fb864adf4 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -56,7 +56,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.FULL_SCREEN_NAVIGATOR)) { return 0; } @@ -161,7 +161,7 @@ function goBack(fallbackRoute: Route, shouldEnforceFallback = false, shouldPopTo 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 (lastRoute?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR) { navigationRef.current.goBack(); return; } @@ -212,6 +212,7 @@ function dismissModal(targetReportID?: string) { const rootState = navigationRef.getRootState(); const lastRoute = rootState.routes.at(-1); switch (lastRoute?.name) { + case NAVIGATORS.FULL_SCREEN_NAVIGATOR: case NAVIGATORS.RIGHT_MODAL_NAVIGATOR: case SCREENS.NOT_FOUND: case SCREENS.REPORT_ATTACHMENTS: diff --git a/src/libs/Navigation/NavigationRoot.tsx b/src/libs/Navigation/NavigationRoot.tsx index 7c0b9ef4fc8c..7ad924d967f7 100644 --- a/src/libs/Navigation/NavigationRoot.tsx +++ b/src/libs/Navigation/NavigationRoot.tsx @@ -75,6 +75,7 @@ function NavigationRoot({authenticated, onReady}: NavigationRootProps) { if (!navigationRef.isReady() || !authenticated) { return; } + // TODO: Add force rehydration! // We need to force state rehydration so the CustomRouter can add the CentralPaneNavigator route if necessary. navigationRef.resetRoot(navigationRef.getRootState()); }, [isSmallScreenWidth, authenticated]); diff --git a/src/libs/Navigation/getTopmostCentralPanePath.ts b/src/libs/Navigation/getTopmostCentralPanePath.ts new file mode 100644 index 000000000000..752bb8390607 --- /dev/null +++ b/src/libs/Navigation/getTopmostCentralPanePath.ts @@ -0,0 +1,19 @@ +import {NavigationState, PartialState} from '@react-navigation/native'; +import SCREENS from '@src/SCREENS'; + +// Get the name of topmost report in the navigation stack. +function getTopmostCentralPanePath(state: NavigationState | PartialState): string | undefined { + if (!state) { + return; + } + + const topmostCentralPane = state.routes.filter((route) => typeof route !== 'number' && 'name' in route && route.name === SCREENS.SETTINGS_CENTRAL_PANE).at(-1); + + if (!topmostCentralPane || typeof topmostCentralPane === 'number' || !('state' in topmostCentralPane)) { + return; + } + + return topmostCentralPane.state?.routes.at(-1)?.path; +} + +export default getTopmostCentralPanePath; diff --git a/src/libs/Navigation/linkingConfig.ts b/src/libs/Navigation/linkingConfig.ts index e9e76f4a2e82..dd0b328a3222 100644 --- a/src/libs/Navigation/linkingConfig.ts +++ b/src/libs/Navigation/linkingConfig.ts @@ -49,17 +49,6 @@ const linkingConfig: LinkingOptions = { screens: { [SCREENS.RIGHT_MODAL.SETTINGS]: { screens: { - [SCREENS.SETTINGS.ROOT]: { - path: ROUTES.SETTINGS, - }, - [SCREENS.SETTINGS.WORKSPACES]: { - path: ROUTES.SETTINGS_WORKSPACES, - exact: true, - }, - [SCREENS.SETTINGS.PREFERENCES.ROOT]: { - path: ROUTES.SETTINGS_PREFERENCES, - exact: true, - }, [SCREENS.SETTINGS.PREFERENCES.PRIORITY_MODE]: { path: ROUTES.SETTINGS_PRIORITY_MODE, exact: true, @@ -76,14 +65,6 @@ const linkingConfig: LinkingOptions = { path: ROUTES.SETTINGS_CLOSE, exact: true, }, - [SCREENS.SETTINGS.SECURITY]: { - path: ROUTES.SETTINGS_SECURITY, - exact: true, - }, - [SCREENS.SETTINGS.WALLET.ROOT]: { - path: ROUTES.SETTINGS_WALLET, - exact: true, - }, [SCREENS.SETTINGS.WALLET.DOMAIN_CARD]: { path: ROUTES.SETTINGS_WALLET_DOMAINCARD.route, exact: true, @@ -140,10 +121,6 @@ const linkingConfig: LinkingOptions = { path: ROUTES.SETTINGS_ADD_BANK_ACCOUNT, exact: true, }, - [SCREENS.SETTINGS.PROFILE.ROOT]: { - path: ROUTES.SETTINGS_PROFILE, - exact: true, - }, [SCREENS.SETTINGS.PROFILE.PRONOUNS]: { path: ROUTES.SETTINGS_PRONOUNS, exact: true, @@ -160,10 +137,6 @@ const linkingConfig: LinkingOptions = { path: ROUTES.SETTINGS_TIMEZONE_SELECT, exact: true, }, - [SCREENS.SETTINGS.ABOUT]: { - path: ROUTES.SETTINGS_ABOUT, - exact: true, - }, [SCREENS.SETTINGS.APP_DOWNLOAD_LINKS]: { path: ROUTES.SETTINGS_APP_DOWNLOAD_LINKS, exact: true, @@ -206,10 +179,6 @@ const linkingConfig: LinkingOptions = { path: ROUTES.SETTINGS_2FA.route, exact: true, }, - [SCREENS.SETTINGS.SHARE_CODE]: { - path: ROUTES.SETTINGS_SHARE_CODE, - exact: true, - }, [SCREENS.SETTINGS.PROFILE.STATUS]: { path: ROUTES.SETTINGS_STATUS, exact: true, @@ -486,6 +455,47 @@ const linkingConfig: LinkingOptions = { }, }, }, + + [NAVIGATORS.FULL_SCREEN_NAVIGATOR]: { + initialRouteName: SCREENS.SETTINGS_HOME, + screens: { + [SCREENS.SETTINGS_HOME]: { + path: ROUTES.SETTINGS_HOME, + }, + [SCREENS.SETTINGS_CENTRAL_PANE]: { + screens: { + [SCREENS.SETTINGS.SHARE_CODE]: { + path: ROUTES.SETTINGS_SHARE_CODE, + exact: true, + }, + [SCREENS.SETTINGS.WORKSPACES]: { + path: ROUTES.SETTINGS_WORKSPACES, + exact: true, + }, + [SCREENS.SETTINGS.PROFILE.ROOT]: { + path: ROUTES.SETTINGS_PROFILE, + exact: true, + }, + [SCREENS.SETTINGS.PREFERENCES.ROOT]: { + path: ROUTES.SETTINGS_PREFERENCES, + exact: true, + }, + [SCREENS.SETTINGS.SECURITY]: { + path: ROUTES.SETTINGS_SECURITY, + exact: true, + }, + [SCREENS.SETTINGS.WALLET.ROOT]: { + path: ROUTES.SETTINGS_WALLET, + exact: true, + }, + [SCREENS.SETTINGS.ABOUT]: { + path: ROUTES.SETTINGS_ABOUT, + exact: true, + }, + }, + }, + }, + }, }, }, }; diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index b69552f6fe0f..f6a10298530f 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -359,6 +359,21 @@ type RightModalNavigatorParamList = { [SCREENS.RIGHT_MODAL.PRIVATE_NOTES]: NavigatorScreenParams; }; +type SettingsCentralPaneNavigatorParamList = { + [SCREENS.SETTINGS.SHARE_CODE]: undefined; + [SCREENS.SETTINGS.WORKSPACES]: undefined; + [SCREENS.SETTINGS.PROFILE.ROOT]: undefined; + [SCREENS.SETTINGS.PREFERENCES.ROOT]: undefined; + [SCREENS.SETTINGS.SECURITY]: undefined; + [SCREENS.SETTINGS.WALLET.ROOT]: undefined; + [SCREENS.SETTINGS.ABOUT]: undefined; +}; + +type FullScreenNavigatorParamList = { + [SCREENS.SETTINGS_HOME]: undefined; + [SCREENS.SETTINGS_CENTRAL_PANE]: NavigatorScreenParams; +}; + type PublicScreensParamList = { [SCREENS.HOME]: undefined; [SCREENS.TRANSITION_BETWEEN_APPS]: { @@ -406,6 +421,7 @@ type AuthScreensParamList = { }; [SCREENS.NOT_FOUND]: undefined; [NAVIGATORS.RIGHT_MODAL_NAVIGATOR]: NavigatorScreenParams; + [NAVIGATORS.FULL_SCREEN_NAVIGATOR]: NavigatorScreenParams; [SCREENS.DESKTOP_SIGN_IN_REDIRECT]: undefined; [CONST.DEMO_PAGES.MONEY2020]: undefined; }; diff --git a/src/pages/ShareCodePage.js b/src/pages/ShareCodePage.js index c63db694159f..8a3ba71082a5 100644 --- a/src/pages/ShareCodePage.js +++ b/src/pages/ShareCodePage.js @@ -81,11 +81,13 @@ class ShareCodePage extends React.Component { Navigation.goBack(isReport ? ROUTES.REPORT_WITH_ID_DETAILS.getRoute(this.props.report.reportID) : ROUTES.SETTINGS)} + onBackButtonPress={() => Navigation.goBack(isReport ? ROUTES.REPORT_WITH_ID_DETAILS.getRoute(this.props.report.reportID) : ROUTES.SETTINGS_HOME)} + shouldUseCentralPaneView + shouldShowBorderBottom /> - + Navigation.goBack(ROUTES.SETTINGS)} + onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_HOME)} + shouldUseCentralPaneView + shouldShowBorderBottom /> diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js index 2af5b145caab..6513d2e2b05e 100755 --- a/src/pages/settings/InitialSettingsPage.js +++ b/src/pages/settings/InitialSettingsPage.js @@ -1,10 +1,11 @@ +import {useNavigationState} from '@react-navigation/native'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; -import Avatar from '@components/Avatar'; +import AvatarWithImagePicker from '@components/AvatarWithImagePicker'; import bankAccountPropTypes from '@components/bankAccountPropTypes'; import cardPropTypes from '@components/cardPropTypes'; import ConfirmModal from '@components/ConfirmModal'; @@ -22,29 +23,25 @@ import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useLocalize from '@hooks/useLocalize'; import useSingleExecution from '@hooks/useSingleExecution'; import useWaitForNavigation from '@hooks/useWaitForNavigation'; -import * as CardUtils from '@libs/CardUtils'; import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; +import getTopmostCentralPaneName from '@libs/Navigation/getTopmostCentralPanePath'; import Navigation from '@libs/Navigation/Navigation'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import * as ReportUtils from '@libs/ReportUtils'; import * as UserUtils from '@libs/UserUtils'; import walletTermsPropTypes from '@pages/EnablePayments/walletTermsPropTypes'; import {CONTEXT_MENU_TYPES} from '@pages/home/report/ContextMenu/ContextMenuActions'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; -import policyMemberPropType from '@pages/policyMemberPropType'; -import * as ReimbursementAccountProps from '@pages/ReimbursementAccount/reimbursementAccountPropTypes'; import useTheme from '@styles/themes/useTheme'; import useThemeStyles from '@styles/useThemeStyles'; import * as Link from '@userActions/Link'; import * as PaymentMethods from '@userActions/PaymentMethods'; +import * as PersonalDetails from '@userActions/PersonalDetails'; import * as Session from '@userActions/Session'; import * as Wallet from '@userActions/Wallet'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; -import assignedCardPropTypes from './Wallet/assignedCardPropTypes'; const propTypes = { /* Onyx Props */ @@ -55,26 +52,6 @@ const propTypes = { email: PropTypes.string, }), - /** The list of this user's policies */ - policies: PropTypes.objectOf( - PropTypes.shape({ - /** The ID of the policy */ - ID: PropTypes.string, - - /** The name of the policy */ - name: PropTypes.string, - - /** The type of the policy */ - type: PropTypes.string, - - /** The user's role in the policy */ - role: PropTypes.string, - - /** The current action that is waiting to happen on the policy */ - pendingAction: PropTypes.oneOf(_.values(CONST.RED_BRICK_ROAD_PENDING_ACTION)), - }), - ), - /** The user's wallet account */ userWallet: PropTypes.shape({ /** The user's current wallet balance */ @@ -87,9 +64,6 @@ const propTypes = { /** List of user's cards */ fundList: PropTypes.objectOf(cardPropTypes), - /** Bank account attached to free plan */ - reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes, - /** Information about the user accepting the terms for payments */ walletTerms: walletTermsPropTypes, @@ -104,28 +78,19 @@ const propTypes = { }), ), - cardList: PropTypes.objectOf(assignedCardPropTypes), - - /** Members keyed by accountID for all policies */ - allPolicyMembers: PropTypes.objectOf(PropTypes.objectOf(policyMemberPropType)), - ...withLocalizePropTypes, ...withCurrentUserPersonalDetailsPropTypes, }; const defaultProps = { session: {}, - policies: {}, userWallet: { currentBalance: 0, }, - reimbursementAccount: {}, walletTerms: {}, bankAccountList: {}, fundList: null, loginList: {}, - cardList: {}, - allPolicyMembers: {}, ...withCurrentUserPersonalDetailsDefaultProps, }; @@ -136,6 +101,7 @@ function InitialSettingsPage(props) { const waitForNavigate = useWaitForNavigation(); const popoverAnchor = useRef(null); const {translate} = useLocalize(); + const activeRoute = useNavigationState(getTopmostCentralPaneName); const [shouldShowSignoutConfirmModal, setShouldShowSignoutConfirmModal] = useState(false); @@ -165,177 +131,158 @@ function InitialSettingsPage(props) { ); /** - * Retuns a list of default menu items - * @returns {Array} the default menu items + * Retuns a list of menu items data for account section + * @returns {Object} object with translationKey, style and items for the account section */ - const getDefaultMenuItems = useMemo(() => { - const policiesAvatars = _.chain(props.policies) - .filter((policy) => PolicyUtils.shouldShowPolicy(policy, props.network.isOffline)) - .sortBy((policy) => policy.name.toLowerCase()) - .map((policy) => ({ - id: policy.id, - source: policy.avatar || ReportUtils.getDefaultWorkspaceAvatar(policy.name), - name: policy.name, - type: CONST.ICON_TYPE_WORKSPACE, - })) - .value(); - - const policyBrickRoadIndicator = - !_.isEmpty(props.reimbursementAccount.errors) || - _.chain(props.policies) - .filter((policy) => policy && policy.type === CONST.POLICY.TYPE.FREE && policy.role === CONST.POLICY.ROLE.ADMIN) - .some((policy) => PolicyUtils.hasPolicyError(policy) || PolicyUtils.getPolicyBrickRoadIndicatorStatus(policy, props.allPolicyMembers)) - .value() - ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR - : null; + const accountMenuItemsData = useMemo(() => { const profileBrickRoadIndicator = UserUtils.getLoginListBrickRoadIndicator(props.loginList); - const paymentCardList = props.fundList || {}; - return [ - { - translationKey: 'common.shareCode', - icon: Expensicons.QrCode, - action: waitForNavigate(() => { - Navigation.navigate(ROUTES.SETTINGS_SHARE_CODE); - }), - }, - { - translationKey: 'common.workspaces', - icon: Expensicons.Building, - action: waitForNavigate(() => { - Navigation.navigate(ROUTES.SETTINGS_WORKSPACES); - }), - floatRightAvatars: policiesAvatars, - shouldStackHorizontally: true, - avatarSize: CONST.AVATAR_SIZE.SMALLER, - brickRoadIndicator: policyBrickRoadIndicator, - }, - { - translationKey: 'common.profile', - icon: Expensicons.Profile, - action: waitForNavigate(() => { - Navigation.navigate(ROUTES.SETTINGS_PROFILE); - }), - brickRoadIndicator: profileBrickRoadIndicator, - }, - { - translationKey: 'common.preferences', - icon: Expensicons.Gear, - action: waitForNavigate(() => { - Navigation.navigate(ROUTES.SETTINGS_PREFERENCES); - }), - }, - { - translationKey: 'initialSettingsPage.security', - icon: Expensicons.Lock, - action: waitForNavigate(() => { - Navigation.navigate(ROUTES.SETTINGS_SECURITY); - }), - }, - { - translationKey: 'common.wallet', - icon: Expensicons.Wallet, - action: waitForNavigate(() => { - Navigation.navigate(ROUTES.SETTINGS_WALLET); - }), - brickRoadIndicator: - PaymentMethods.hasPaymentMethodError(props.bankAccountList, paymentCardList) || - !_.isEmpty(props.userWallet.errors) || - !_.isEmpty(props.walletTerms.errors) || - CardUtils.hasDetectedFraud(props.cardList) - ? 'error' - : null, - }, - { - translationKey: 'initialSettingsPage.help', - icon: Expensicons.QuestionMark, - action: () => { - Link.openExternalLink(CONST.NEWHELP_URL); + return { + sectionStyle: styles.accountSettingsSectionContainer, + sectionTranslationKey: 'initialSettingsPage.account', + items: [ + { + translationKey: 'common.profile', + icon: Expensicons.Profile, + routeName: ROUTES.SETTINGS_PROFILE, + brickRoadIndicator: profileBrickRoadIndicator, }, - shouldShowRightIcon: true, - iconRight: Expensicons.NewWindow, - link: CONST.NEWHELP_URL, - }, - { - translationKey: 'initialSettingsPage.about', - icon: Expensicons.Info, - action: waitForNavigate(() => { - Navigation.navigate(ROUTES.SETTINGS_ABOUT); - }), - }, - { - translationKey: 'initialSettingsPage.signOut', - icon: Expensicons.Exit, - action: () => { - signOut(false); + { + translationKey: 'common.wallet', + icon: Expensicons.Wallet, + routeName: ROUTES.SETTINGS_WALLET, + brickRoadIndicator: + PaymentMethods.hasPaymentMethodError(props.bankAccountList, paymentCardList) || !_.isEmpty(props.userWallet.errors) || !_.isEmpty(props.walletTerms.errors) + ? 'error' + : null, + }, + { + translationKey: 'common.shareCode', + icon: Expensicons.QrCode, + routeName: ROUTES.SETTINGS_SHARE_CODE, }, + { + translationKey: 'common.preferences', + icon: Expensicons.Gear, + routeName: ROUTES.SETTINGS_PREFERENCES, + }, + { + translationKey: 'initialSettingsPage.security', + icon: Expensicons.Lock, + routeName: ROUTES.SETTINGS_SECURITY, + }, + { + translationKey: 'initialSettingsPage.signOut', + icon: Expensicons.Exit, + action: () => { + signOut(false); + }, + }, + ], + }; + }, [props.bankAccountList, props.fundList, props.loginList, props.userWallet.errors, props.walletTerms.errors, signOut, styles.accountSettingsSectionContainer]); + + /** + * Retuns a list of menu items data for general section + * @returns {Object} object with translationKey, style and items for the general section + */ + const generaltMenuItemsData = useMemo( + () => ({ + sectionStyle: { + ...styles.pt4, }, - ]; - }, [ - props.allPolicyMembers, - props.bankAccountList, - props.cardList, - props.fundList, - props.loginList, - props.network.isOffline, - props.policies, - props.reimbursementAccount.errors, - props.userWallet.errors, - props.walletTerms.errors, - signOut, - waitForNavigate, - ]); + sectionTranslationKey: 'initialSettingsPage.general', + items: [ + { + translationKey: 'initialSettingsPage.help', + icon: Expensicons.QuestionMark, + action: () => { + Link.openExternalLink(CONST.NEWHELP_URL); + }, + shouldShowRightIcon: true, + iconRight: Expensicons.NewWindow, + link: CONST.NEWHELP_URL, + }, + { + translationKey: 'initialSettingsPage.about', + icon: Expensicons.Info, + routeName: ROUTES.SETTINGS_ABOUT, + }, + ], + }), + [styles.pt4], + ); - const getMenuItems = useMemo(() => { - /** - * @param {Boolean} isPaymentItem whether the item being rendered is the payments menu item - * @returns {String|undefined} the user's wallet balance - */ - const getWalletBalance = (isPaymentItem) => (isPaymentItem ? CurrencyUtils.convertToDisplayString(props.userWallet.currentBalance) : undefined); + /** + * Retuns JSX.Element with menu items + * @param {Object} menuItemsData list with menu items data + * @returns {JSX.Element} the menu items for passed data + */ + const getMenuItemsSection = useCallback( + (menuItemsData) => { + /** + * @param {Boolean} isPaymentItem whether the item being rendered is the payments menu item + * @returns {String|undefined} the user's wallet balance + */ + const getWalletBalance = (isPaymentItem) => (isPaymentItem ? CurrencyUtils.convertToDisplayString(props.userWallet.currentBalance) : undefined); + return ( + + {translate(menuItemsData.sectionTranslationKey)} + {_.map(menuItemsData.items, (item, index) => { + const keyTitle = item.translationKey ? translate(item.translationKey) : item.title; + const isPaymentItem = item.translationKey === 'common.wallet'; + + return ( + { + if (item.action) { + item.action(); + } else { + waitForNavigate(() => { + Navigation.navigate(item.routeName); + })(); + } + })} + iconStyles={item.iconStyles} + badgeText={getWalletBalance(isPaymentItem)} + fallbackIcon={item.fallbackIcon} + brickRoadIndicator={item.brickRoadIndicator} + floatRightAvatars={item.floatRightAvatars} + shouldStackHorizontally={item.shouldStackHorizontally} + floatRightAvatarSize={item.avatarSize} + ref={popoverAnchor} + shouldBlockSelection={Boolean(item.link)} + onSecondaryInteraction={ + !_.isEmpty(item.link) ? (e) => ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, item.link, popoverAnchor.current) : undefined + } + focused={activeRoute && activeRoute.startsWith(item.routeName, 1)} + /> + ); + })} + + ); + }, + [styles.pb4, styles.mh3, styles.sectionTitle, styles.sectionMenuItem, translate, props.userWallet.currentBalance, isExecuting, singleExecution, activeRoute, waitForNavigate], + ); - return ( - <> - {_.map(getDefaultMenuItems, (item, index) => { - const keyTitle = item.translationKey ? translate(item.translationKey) : item.title; - const isPaymentItem = item.translationKey === 'common.wallet'; + const accountMenuItems = useMemo(() => getMenuItemsSection(accountMenuItemsData), [accountMenuItemsData, getMenuItemsSection]); + const generalMenuItems = useMemo(() => getMenuItemsSection(generaltMenuItemsData), [generaltMenuItemsData, getMenuItemsSection]); - return ( - ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, item.link, popoverAnchor.current) : undefined - } - /> - ); - })} - - ); - }, [getDefaultMenuItems, props.userWallet.currentBalance, translate, isExecuting, singleExecution]); + const currentUserDetails = props.currentUserPersonalDetails || {}; + const avatarURL = lodashGet(currentUserDetails, 'avatar', ''); + const accountID = lodashGet(currentUserDetails, 'accountID', ''); const headerContent = ( - + {_.isEmpty(props.currentUserPersonalDetails) || _.isUndefined(props.currentUserPersonalDetails.displayName) ? ( - + ) : ( <> @@ -347,11 +294,24 @@ function InitialSettingsPage(props) { role={CONST.ROLE.BUTTON} > - @@ -385,15 +345,20 @@ function InitialSettingsPage(props) { ); + const navigateBackTo = lodashGet(props.route, 'params.backTo', ROUTES.HOME); + return ( Navigation.navigate(navigateBackTo)} backgroundColor={theme.PAGE_THEMES[SCREENS.SETTINGS.ROOT].backgroundColor} + childrenContainerStyles={[styles.m0, styles.p0]} > - {getMenuItems} + {accountMenuItems} + {generalMenuItems} Navigation.goBack(ROUTES.SETTINGS)} + // onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS)} backgroundColor={theme.PAGE_THEMES[SCREENS.SETTINGS.PREFERENCES.ROOT].backgroundColor} illustration={LottieAnimations.PreferencesDJ} + shouldUseCentralPaneView > Navigation.goBack(ROUTES.SETTINGS)} + onBackButtonPress={() => Navigation.goBack(ROUTES)} illustration={LottieAnimations.ExpensifyLounge} > Navigation.goBack(ROUTES.SETTINGS)} + onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_HOME)} + shouldUseCentralPaneView + shouldShowBorderBottom /> - {_.map(profileSettingsOptions, (detail, index) => ( Navigation.goBack(ROUTES.SETTINGS)} + onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_HOME)} shouldShowBackButton illustration={LottieAnimations.Safe} backgroundColor={theme.PAGE_THEMES[SCREENS.SETTINGS.SECURITY].backgroundColor} + shouldUseCentralPaneView > diff --git a/src/pages/settings/Wallet/WalletEmptyState.js b/src/pages/settings/Wallet/WalletEmptyState.js index f4609bdc8534..c533c68c1b15 100644 --- a/src/pages/settings/Wallet/WalletEmptyState.js +++ b/src/pages/settings/Wallet/WalletEmptyState.js @@ -38,7 +38,7 @@ function WalletEmptyState({onAddPaymentMethod}) { Navigation.goBack(ROUTES.SETTINGS)} + onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_HOME)} title={translate('common.wallet')} footer={