diff --git a/Mobile-Expensify b/Mobile-Expensify index 39615dc20653..dce6ea980d1d 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 39615dc206531abec3507fc026bdb5d69d3358fb +Subproject commit dce6ea980d1da919ea7d0c38556b1989fe82ddcc diff --git a/android/app/build.gradle b/android/app/build.gradle index b37622a29358..8cb94527e91c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -111,8 +111,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009040204 - versionName "9.4.2-4" + versionCode 1009040205 + versionName "9.4.2-5" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 41f05dafa106..305cea95b447 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -44,7 +44,7 @@ CFBundleVersion - 9.4.2.4 + 9.4.2.5 FullStory OrgId diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index f054a275fb32..41315030de31 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.4.2 CFBundleVersion - 9.4.2.4 + 9.4.2.5 NSExtension NSExtensionPointIdentifier diff --git a/ios/ShareViewController/Info.plist b/ios/ShareViewController/Info.plist index d3d4a4cfcd86..32e8d646c18d 100644 --- a/ios/ShareViewController/Info.plist +++ b/ios/ShareViewController/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.4.2 CFBundleVersion - 9.4.2.4 + 9.4.2.5 NSExtension NSExtensionAttributes diff --git a/package-lock.json b/package-lock.json index 311e9c0d57ee..33ab9047f5fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.4.2-4", + "version": "9.4.2-5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.4.2-4", + "version": "9.4.2-5", "hasInstallScript": true, "license": "MIT", "workspaces": [ diff --git a/package.json b/package.json index ec8b9fe0c1dd..a553e030f3e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.4.2-4", + "version": "9.4.2-5", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", diff --git a/src/ROUTES.ts b/src/ROUTES.ts index ccc870861aaf..c529fe8a3825 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -821,10 +821,6 @@ const DYNAMIC_ROUTES = { entryScreens: [SCREENS.REPORT, SCREENS.RIGHT_MODAL.SEARCH_REPORT, SCREENS.RIGHT_MODAL.EXPENSE_REPORT, SCREENS.RIGHT_MODAL.SEARCH_MONEY_REQUEST_REPORT], getRoute: (reportID: string, reportActionID: string) => `flag/${reportID}/${reportActionID}`, }, - WORKSPACE_REPORT_FIELDS_INITIAL_LIST_VALUE: { - path: 'initial-list-value', - entryScreens: [SCREENS.WORKSPACE.REPORT_FIELDS_CREATE], - }, } as const satisfies DynamicRoutes; const ROUTES = { diff --git a/src/SCREENS.ts b/src/SCREENS.ts index a8f15dfe8b1e..87846db73b96 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -795,7 +795,6 @@ const SCREENS = { REPORT_FIELDS_VALUE_SETTINGS: 'Workspace_ReportFields_ValueSettings', REPORT_FIELDS_EDIT_VALUE: 'Workspace_ReportFields_EditValue', REPORT_FIELDS_EDIT_INITIAL_VALUE: 'Workspace_ReportFields_EditInitialValue', - DYNAMIC_REPORT_FIELDS_INITIAL_LIST_VALUE: 'Dynamic_Report_Fields_Initial_List_Value', TAX_EDIT: 'Workspace_Tax_Edit', TAX_NAME: 'Workspace_Tax_Name', TAX_VALUE: 'Workspace_Tax_Value', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 9a04e5227ff5..6c85e957e7c3 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -995,8 +995,6 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/Subscription/PaymentCard').default), [SCREENS.WORKSPACE.REPORT_FIELDS_CREATE]: () => require('../../../../pages/workspace/reports/CreateReportFieldsPage').default, - [SCREENS.WORKSPACE.DYNAMIC_REPORT_FIELDS_INITIAL_LIST_VALUE]: () => - require('../../../../pages/workspace/reports/InitialListValueSelector/DynamicReportFieldsInitialListValuePage').default, [SCREENS.WORKSPACE.REPORT_FIELDS_SETTINGS]: () => require('../../../../pages/workspace/reports/ReportFieldsSettingsPage').default, [SCREENS.WORKSPACE.REPORT_FIELDS_LIST_VALUES]: () => require('../../../../pages/workspace/reports/ReportFieldsListValuesPage').default, [SCREENS.WORKSPACE.REPORT_FIELDS_ADD_VALUE]: () => require('../../../../pages/workspace/reports/ReportFieldsAddListValuePage').default, diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts index 11996b30806c..61591fffedac 100755 --- a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts @@ -295,7 +295,6 @@ const WORKSPACE_TO_RHP: Partial['config'] = { [SCREENS.WORKSPACE.REPORT_FIELDS_CREATE]: { path: ROUTES.WORKSPACE_CREATE_REPORT_FIELD.route, }, - [SCREENS.WORKSPACE.DYNAMIC_REPORT_FIELDS_INITIAL_LIST_VALUE]: DYNAMIC_ROUTES.WORKSPACE_REPORT_FIELDS_INITIAL_LIST_VALUE.path, [SCREENS.WORKSPACE.REPORT_FIELDS_LIST_VALUES]: { path: ROUTES.WORKSPACE_REPORT_FIELDS_LIST_VALUES.route, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 77ca6538a3da..7286e5b88eb2 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -644,9 +644,6 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.REPORT_FIELDS_CREATE]: { policyID: string; }; - [SCREENS.WORKSPACE.DYNAMIC_REPORT_FIELDS_INITIAL_LIST_VALUE]: { - policyID: string; - }; [SCREENS.WORKSPACE.REPORT_FIELDS_LIST_VALUES]: { policyID: string; reportFieldID?: string; diff --git a/src/pages/workspace/reports/InitialListValueSelector/DynamicReportFieldsInitialListValuePage.tsx b/src/pages/workspace/reports/InitialListValueSelector/DynamicReportFieldsInitialListValuePage.tsx deleted file mode 100644 index be0ed13553d4..000000000000 --- a/src/pages/workspace/reports/InitialListValueSelector/DynamicReportFieldsInitialListValuePage.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React, {useEffect} from 'react'; -import {View} from 'react-native'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import ScreenWrapper from '@components/ScreenWrapper'; -import Text from '@components/Text'; -import useDynamicBackPath from '@hooks/useDynamicBackPath'; -import useLocalize from '@hooks/useLocalize'; -import useOnyx from '@hooks/useOnyx'; -import useThemeStyles from '@hooks/useThemeStyles'; -import {setDraftValues} from '@libs/actions/FormActions'; -import Navigation from '@libs/Navigation/Navigation'; -import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; -import {hasAccountingConnections} from '@libs/PolicyUtils'; -import type {SettingsNavigatorParamList} from '@navigation/types'; -import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; -import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; -import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES, {DYNAMIC_ROUTES} from '@src/ROUTES'; -import type SCREENS from '@src/SCREENS'; -import INPUT_IDS from '@src/types/form/WorkspaceReportFieldForm'; -import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; -import ReportFieldsInitialListValuePicker from './ReportFieldsInitialListValuePicker'; - -type DynamicReportFieldsInitialListValuePageProps = WithPolicyAndFullscreenLoadingProps & - PlatformStackScreenProps; - -function DynamicReportFieldsInitialListValuePage({ - policy, - route: { - params: {policyID}, - }, -}: DynamicReportFieldsInitialListValuePageProps) { - const styles = useThemeStyles(); - const {translate} = useLocalize(); - const [formDraft, formDraftMetadata] = useOnyx(ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM_DRAFT); - const backPath = useDynamicBackPath(DYNAMIC_ROUTES.WORKSPACE_REPORT_FIELDS_INITIAL_LIST_VALUE.path); - - const currentValue = formDraft?.[INPUT_IDS.INITIAL_VALUE] ?? ''; - const listValues = formDraft?.[INPUT_IDS.LIST_VALUES] ?? []; - - // When this page is reached via deeplink or restored after a page refresh, the parent CreateReportFieldsPage resets - // the create-field draft on mount, leaving no list values to choose from. In that case, return to the Add field - // page instead of stranding the user on an empty picker. On refresh the dynamic route restores without its parent - // in the stack, so the dynamic back path resolves to the report fields settings page rather than the Add field page - // — target the Add field route explicitly to land there. - useEffect(() => { - if (isLoadingOnyxValue(formDraftMetadata) || listValues.length > 0) { - return; - } - Navigation.goBack(ROUTES.WORKSPACE_CREATE_REPORT_FIELD.getRoute(policyID)); - }, [formDraftMetadata, listValues.length, policyID]); - - const onValueSelected = (value: string) => { - setDraftValues(ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM, { - [INPUT_IDS.INITIAL_VALUE]: currentValue === value ? '' : value, - }); - Navigation.goBack(backPath); - }; - - return ( - - - Navigation.goBack(backPath)} - /> - - {translate('workspace.reportFields.listValuesInputSubtitle')} - - - - - ); -} - -export default withPolicyAndFullscreenLoading(DynamicReportFieldsInitialListValuePage); diff --git a/src/pages/workspace/reports/InitialListValueSelector/InitialListValueSelectorModal.tsx b/src/pages/workspace/reports/InitialListValueSelector/InitialListValueSelectorModal.tsx new file mode 100644 index 000000000000..a6d39b3ee6cf --- /dev/null +++ b/src/pages/workspace/reports/InitialListValueSelector/InitialListValueSelectorModal.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import {View} from 'react-native'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import Modal from '@components/Modal'; +import ScreenWrapper from '@components/ScreenWrapper'; +import Text from '@components/Text'; +import useOnyx from '@hooks/useOnyx'; +import useThemeStyles from '@hooks/useThemeStyles'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ReportFieldsInitialListValuePicker from './ReportFieldsInitialListValuePicker'; + +type InitialListValueSelectorModalProps = { + /** Whether the modal is visible */ + isVisible: boolean; + + /** Selected value */ + currentValue: string; + + /** Label to display on field */ + label: string; + + /** Subtitle to display on field */ + subtitle: string; + + /** Function to call when the user selects a value */ + onValueSelected: (value: string) => void; + + /** Function to call when the user closes the value selector modal */ + onClose: () => void; +}; + +function InitialListValueSelectorModal({isVisible, currentValue, label, subtitle, onValueSelected, onClose}: InitialListValueSelectorModalProps) { + const styles = useThemeStyles(); + + const [formDraft] = useOnyx(ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM_DRAFT); + + return ( + + + + + {subtitle} + + + + + ); +} + +export default InitialListValueSelectorModal; diff --git a/src/pages/workspace/reports/InitialListValueSelector/ReportFieldsInitialListValuePicker.tsx b/src/pages/workspace/reports/InitialListValueSelector/ReportFieldsInitialListValuePicker.tsx index 9592234e92ff..e57a8b862b06 100644 --- a/src/pages/workspace/reports/InitialListValueSelector/ReportFieldsInitialListValuePicker.tsx +++ b/src/pages/workspace/reports/InitialListValueSelector/ReportFieldsInitialListValuePicker.tsx @@ -39,7 +39,6 @@ function ReportFieldsInitialListValuePicker({listValues, disabledOptions, value, ListItem={SingleSelectListItem} onSelectRow={(item) => onValueChange(item.value)} initiallyFocusedItemKey={listValueOptions.find((listValue) => listValue.isSelected)?.keyForList} - shouldSingleExecuteRowSelect addBottomSafeAreaPadding /> ); diff --git a/src/pages/workspace/reports/InitialListValueSelector/index.tsx b/src/pages/workspace/reports/InitialListValueSelector/index.tsx index fac4128b34a5..0ea7cb414b64 100644 --- a/src/pages/workspace/reports/InitialListValueSelector/index.tsx +++ b/src/pages/workspace/reports/InitialListValueSelector/index.tsx @@ -1,19 +1,21 @@ import type {ForwardedRef} from 'react'; -import React, {useEffect} from 'react'; +import React, {useEffect, useState} from 'react'; import {View} from 'react-native'; import type {MenuItemBaseProps} from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import useOnyx from '@hooks/useOnyx'; -import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute'; -import Navigation from '@libs/Navigation/Navigation'; +import blurActiveElement from '@libs/Accessibility/blurActiveElement'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {DYNAMIC_ROUTES} from '@src/ROUTES'; +import InitialListValueSelectorModal from './InitialListValueSelectorModal'; type InitialListValueSelectorProps = Pick & { /** Currently selected value */ value?: string; + /** Subtitle to display on field */ + subtitle?: string; + /** Function to call when the user selects a value */ onInputChange?: (value: string) => void; @@ -21,9 +23,25 @@ type InitialListValueSelectorProps = Pick; }; -function InitialListValueSelector({value = '', label = '', rightLabel, errorText = '', onInputChange, ref}: InitialListValueSelectorProps) { +function InitialListValueSelector({value = '', label = '', rightLabel, subtitle = '', errorText = '', onInputChange, ref}: InitialListValueSelectorProps) { const [formDraft] = useOnyx(ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM_DRAFT); + const [isPickerVisible, setIsPickerVisible] = useState(false); + + const showPickerModal = () => { + setIsPickerVisible(true); + }; + + const hidePickerModal = () => { + setIsPickerVisible(false); + blurActiveElement(); + }; + + const updateValueInput = (initialValue: string) => { + onInputChange?.(value === initialValue ? '' : initialValue); + hidePickerModal(); + }; + useEffect(() => { const currentValueIndex = Object.values(formDraft?.listValues ?? {}).findIndex((listValue) => listValue === value); const isCurrentValueDisabled = formDraft?.disabledListValues?.[currentValueIndex] ?? true; @@ -43,7 +61,15 @@ function InitialListValueSelector({value = '', label = '', rightLabel, errorText rightLabel={rightLabel} brickRoadIndicator={errorText ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={errorText} - onPress={() => Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.WORKSPACE_REPORT_FIELDS_INITIAL_LIST_VALUE.path))} + onPress={showPickerModal} + /> + );