diff --git a/src/CONST.ts b/src/CONST.ts index 4f177d2294d..54cc4f78c1c 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2045,6 +2045,7 @@ const CONST = { OPTIMISTIC_TRANSACTION_ID: '1', // Note: These payment types are used when building IOU reportAction message values in the server and should // not be changed. + LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS: 7, PAYMENT_TYPE: { ELSEWHERE: 'Elsewhere', EXPENSIFY: 'Expensify', diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index df1413620c2..2a325bb4a74 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -133,6 +133,9 @@ const ONYXKEYS = { /** The NVP with the last payment method used per policy */ NVP_LAST_PAYMENT_METHOD: 'nvp_private_lastPaymentMethod', + /** Last date (yyyy-MM-dd HH:mm:ss) when the location permission prompt was shown. */ + NVP_LAST_LOCATION_PERMISSION_PROMPT: 'nvp_lastLocalPermissionPrompt', + /** This NVP holds to most recent waypoints that a person has used when creating a distance expense */ NVP_RECENT_WAYPOINTS: 'nvp_expensify_recentWaypoints', @@ -903,6 +906,7 @@ type OnyxValuesMapping = { [ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION]: boolean; [ONYXKEYS.FOCUS_MODE_NOTIFICATION]: boolean; [ONYXKEYS.NVP_LAST_PAYMENT_METHOD]: OnyxTypes.LastPaymentMethod; + [ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT]: string; [ONYXKEYS.LAST_EXPORT_METHOD]: OnyxTypes.LastExportMethod; [ONYXKEYS.NVP_RECENT_WAYPOINTS]: OnyxTypes.RecentWaypoint[]; [ONYXKEYS.NVP_INTRO_SELECTED]: OnyxTypes.IntroSelected; diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts index 2de905ff604..93ba11e14e5 100644 --- a/src/libs/DateUtils.ts +++ b/src/libs/DateUtils.ts @@ -3,6 +3,7 @@ import { addHours, addMilliseconds, addMinutes, + differenceInDays, eachDayOfInterval, eachMonthOfInterval, endOfDay, @@ -825,6 +826,25 @@ function isCardExpired(expiryMonth: number, expiryYear: number): boolean { return expiryYear < currentYear || (expiryYear === currentYear && expiryMonth < currentMonth); } +/** + * Returns the difference in the number of days from the provided date to/from now. + * @param - The date to compare. + * @returns The difference in days as an integer. + */ +function getDifferenceInDaysFromNow(date: Date) { + return differenceInDays(new Date(), date); +} + +/** + * Returns a boolean value indicating whether the provided date string can be parsed as a valid date. + * @param dateString string + * @returns True if the date string is valid, otherwise false. + */ +function isValidDateString(dateString: string) { + const date = new Date(dateString); + return !Number.isNaN(date.getTime()); +} + const DateUtils = { isDate, formatToDayOfWeek, @@ -870,6 +890,8 @@ const DateUtils = { getFormattedTransportDate, doesDateBelongToAPastYear, isCardExpired, + getDifferenceInDaysFromNow, + isValidDateString, }; export default DateUtils; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 8d8e25a3ffb..71e31d73043 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -8285,6 +8285,10 @@ function mergeDuplicates(params: TransactionMergeParams) { API.write(WRITE_COMMANDS.TRANSACTION_MERGE, params, {optimisticData, failureData}); } +function updateLastLocationPermissionPrompt() { + Onyx.set(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, new Date().toISOString()); +} + /** Instead of merging the duplicates, it updates the transaction we want to keep and puts the others on hold without deleting them */ function resolveDuplicates(params: TransactionMergeParams) { const originalSelectedTransaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${params.transactionID}`]; @@ -8475,6 +8479,7 @@ export { updateMoneyRequestTaxAmount, updateMoneyRequestTaxRate, mergeDuplicates, + updateLastLocationPermissionPrompt, resolveDuplicates, }; export type {GPSPoint as GpsPoint, IOURequestType}; diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 752a5082250..7cbb2327c5c 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -15,6 +15,7 @@ import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import DateUtils from '@libs/DateUtils'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as FileUtils from '@libs/fileDownload/FileUtils'; import getCurrentPosition from '@libs/getCurrentPosition'; @@ -74,6 +75,7 @@ function IOURequestStepConfirmation({ const [receiptFile, setReceiptFile] = useState>(); const requestType = TransactionUtils.getRequestType(transaction); const isDistanceRequest = requestType === CONST.IOU.REQUEST_TYPE.DISTANCE; + const [lastLocationPermissionPrompt] = useOnyx(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT); const receiptFilename = transaction?.filename; const receiptPath = transaction?.receipt?.source; @@ -580,9 +582,17 @@ function IOURequestStepConfirmation({ const onConfirm = (listOfParticipants: Participant[]) => { setSelectedParticipantList(listOfParticipants); + if (gpsRequired) { - setStartLocationPermissionFlow(true); - return; + const shouldStartLocationPermissionFlow = + !lastLocationPermissionPrompt || + (DateUtils.isValidDateString(lastLocationPermissionPrompt ?? '') && + DateUtils.getDifferenceInDaysFromNow(new Date(lastLocationPermissionPrompt ?? '')) > CONST.IOU.LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS); + + if (shouldStartLocationPermissionFlow) { + setStartLocationPermissionFlow(true); + return; + } } createTransaction(listOfParticipants); @@ -617,7 +627,10 @@ function IOURequestStepConfirmation({ startPermissionFlow={startLocationPermissionFlow} resetPermissionFlow={() => setStartLocationPermissionFlow(false)} onGrant={() => createTransaction(selectedParticipantList, true)} - onDeny={() => createTransaction(selectedParticipantList, false)} + onDeny={() => { + IOU.updateLastLocationPermissionPrompt(); + createTransaction(selectedParticipantList, false); + }} /> )}