diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 2e699d185574c..430e3a7875f38 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -10,6 +10,7 @@
+
UIBackgroundModes
+ location
remote-notification
fetch
processing
diff --git a/src/languages/de.ts b/src/languages/de.ts
index 736eab6999d3e..047473595cb5d 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -7958,6 +7958,19 @@ Hier ist ein *Testbeleg*, um dir zu zeigen, wie es funktioniert:`,
confirm: 'Entfernungsverfolgung verwerfen',
},
zeroDistanceTripModal: {title: 'Ausgabe kann nicht erstellt werden', prompt: 'Sie können keine Ausgabe mit demselben Start- und Zielort erstellen.'},
+ locationRequiredModal: {
+ title: 'Standortzugriff erforderlich',
+ prompt: 'Bitte erlaube den Standortzugriff in den Einstellungen deines Geräts, um die GPS-Distanzverfolgung zu starten.',
+ allow: 'Erlauben',
+ },
+ androidBackgroundLocationRequiredModal: {
+ title: 'Zugriff auf den Standort im Hintergrund erforderlich',
+ prompt: 'Bitte erlaube den Zugriff auf den Standort im Hintergrund in den Geräteeinstellungen (Option „Immer zulassen“), um die GPS-Distanzverfolgung zu starten.',
+ },
+ preciseLocationRequiredModal: {
+ title: 'Genaue Position erforderlich',
+ prompt: 'Bitte aktiviere „genaue Standortbestimmung“ in den Einstellungen deines Geräts, um die GPS‑Streckenverfolgung zu starten.',
+ },
desktop: {
title: 'Entfernung auf deinem Handy verfolgen',
subtitle: 'Protokolliere Meilen oder Kilometer automatisch mit GPS und verwandle Fahrten sofort in Ausgaben.',
diff --git a/src/languages/en.ts b/src/languages/en.ts
index a0c754e62dbbc..c4876bc1cfb46 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -7035,6 +7035,19 @@ const translations = {
title: "Can't create expense",
prompt: "You can't create an expense with the same start and stop location.",
},
+ locationRequiredModal: {
+ title: 'Location access required',
+ prompt: 'Please allow location access in your device settings to start GPS distance tracking.',
+ allow: 'Allow',
+ },
+ androidBackgroundLocationRequiredModal: {
+ title: 'Background location access required',
+ prompt: 'Please allow background location access in your device settings ("Allow all the time" option) to start GPS distance tracking.',
+ },
+ preciseLocationRequiredModal: {
+ title: 'Precise location required',
+ prompt: 'Please enable "precise location" in your device settings to start GPS distance tracking.',
+ },
desktop: {
title: 'Track distance on your phone',
subtitle: 'Log miles or kilometers automatically with GPS and turn trips into expenses instantly.',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 2ed90a809ac4d..892d40f9d127d 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -8003,6 +8003,19 @@ ${amount} para ${merchant} - ${date}`,
title: 'No se puede crear el gasto',
prompt: 'No puedes crear un gasto con la misma ubicación de inicio y fin.',
},
+ locationRequiredModal: {
+ title: 'Se requiere acceso a la ubicación',
+ prompt: 'Por favor, permite el acceso a la ubicación en la configuración de tu dispositivo para iniciar el seguimiento de distancia por GPS.',
+ allow: 'Permitir',
+ },
+ androidBackgroundLocationRequiredModal: {
+ title: 'Se requiere acceso a la ubicación en segundo plano',
+ prompt: 'Por favor, permite el acceso a la ubicación en segundo plano en la configuración de tu dispositivo (opción "Permitir solo con la app en uso") para iniciar el seguimiento de distancia por GPS.',
+ },
+ preciseLocationRequiredModal: {
+ title: 'Se requiere ubicación precisa',
+ prompt: 'Por favor, habilita la "ubicación precisa" en la configuración de tu dispositivo para iniciar el seguimiento de distancia por GPS.',
+ },
desktop: {
title: 'Registra la distancia en tu teléfono',
subtitle: 'Registra millas o kilómetros automáticamente con GPS y convierte los viajes en gastos al instante.',
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 6974bab3986c7..d807b26092aac 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -7963,6 +7963,19 @@ Voici un *reçu test* pour vous montrer comment cela fonctionne :`,
confirm: 'Ignorer le suivi de la distance',
},
zeroDistanceTripModal: {title: 'Impossible de créer la dépense', prompt: 'Vous ne pouvez pas créer une dépense avec le même lieu de départ et d’arrivée.'},
+ locationRequiredModal: {
+ title: 'Accès à la localisation requis',
+ prompt: 'Veuillez autoriser l’accès à la localisation dans les paramètres de votre appareil pour lancer le suivi de distance GPS.',
+ allow: 'Autoriser',
+ },
+ androidBackgroundLocationRequiredModal: {
+ title: 'Accès à la position en arrière-plan requis',
+ prompt: 'Veuillez autoriser l’accès à la localisation en arrière-plan dans les paramètres de votre appareil (option « Autoriser tout le temps ») pour démarrer le suivi de distance par GPS.',
+ },
+ preciseLocationRequiredModal: {
+ title: 'Emplacement précis requis',
+ prompt: 'Veuillez activer la « localisation précise » dans les paramètres de votre appareil pour commencer le suivi de distance GPS.',
+ },
desktop: {
title: 'Suivez la distance sur votre téléphone',
subtitle: 'Enregistrez automatiquement les miles ou kilomètres avec le GPS et transformez instantanément vos trajets en dépenses.',
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 0c6a41bf918b6..4d0e0904e3175 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -7941,6 +7941,19 @@ Ecco una *ricevuta di prova* per mostrarti come funziona:`,
confirm: 'Scarta monitoraggio distanza',
},
zeroDistanceTripModal: {title: 'Impossibile creare la spesa', prompt: 'Non puoi creare una spesa con la stessa località di partenza e di arrivo.'},
+ locationRequiredModal: {
+ title: 'Accesso alla posizione richiesto',
+ prompt: 'Consenti l’accesso alla posizione nelle impostazioni del dispositivo per avviare il tracciamento della distanza GPS.',
+ allow: 'Consenti',
+ },
+ androidBackgroundLocationRequiredModal: {
+ title: 'Accesso alla posizione in background richiesto',
+ prompt: 'Consenti l’accesso alla posizione in background nelle impostazioni del dispositivo (opzione “Consenti sempre”) per avviare il tracciamento della distanza tramite GPS.',
+ },
+ preciseLocationRequiredModal: {
+ title: 'Posizione precisa richiesta',
+ prompt: 'Per favore, abilita la “posizione precisa” nelle impostazioni del dispositivo per avviare il tracciamento della distanza GPS.',
+ },
desktop: {
title: 'Tieni traccia della distanza sul tuo telefono',
subtitle: 'Registra automaticamente miglia o chilometri con il GPS e trasforma i viaggi in spese all’istante.',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 385fc00e960be..239c5f4f4ac9c 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -7870,6 +7870,12 @@ Expensify の使い方をお見せするための*テストレシート*がこ
stopGpsTrackingModal: {title: 'GPS追跡を停止', prompt: '本当に終了しますか?現在のジャーニーが終了します。', cancel: '追跡を再開', confirm: 'GPS追跡を停止'},
discardDistanceTrackingModal: {title: '距離の追跡を破棄', prompt: '本当に実行しますか?現在の行程が破棄され、元に戻すことはできません。', confirm: '距離の追跡を破棄'},
zeroDistanceTripModal: {title: '経費を作成できません', prompt: '開始地点と終了地点が同じ経路では経費を作成できません。'},
+ locationRequiredModal: {title: '位置情報へのアクセスが必要です', prompt: 'GPS で距離を追跡するには、デバイスの設定で位置情報へのアクセスを許可してください。', allow: '許可'},
+ androidBackgroundLocationRequiredModal: {
+ title: 'バックグラウンド位置情報へのアクセスが必要です',
+ prompt: 'GPS距離の追跡を開始するには、デバイスの設定でバックグラウンドの位置情報アクセスを許可し(「常に許可」オプション)、有効にしてください。',
+ },
+ preciseLocationRequiredModal: {title: '正確な位置情報が必要です', prompt: 'GPS距離の追跡を開始するには、デバイスの設定で「正確な位置情報」を有効にしてください。'},
desktop: {title: 'スマートフォンで距離を記録する', subtitle: 'GPS で自動的にマイルまたはキロメートルを記録し、移動をすぐに経費に変換します。', button: 'アプリをダウンロード'},
},
};
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index d405ca615944b..17e2bb30e0ab7 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -7927,6 +7927,17 @@ Hier is een *testbon* om je te laten zien hoe het werkt:`,
confirm: 'Afstandstracking negeren',
},
zeroDistanceTripModal: {title: 'Kan geen uitgave aanmaken', prompt: 'Je kunt geen uitgave aanmaken met dezelfde begin- en eindlocatie.'},
+
+ locationRequiredModal: {
+ title: 'Locatietoegang vereist',
+ prompt: 'Sta locatietoegang toe in de instellingen van je apparaat om GPS-afstandsregistratie te starten.',
+ allow: 'Toestaan',
+ },
+ androidBackgroundLocationRequiredModal: {
+ title: 'Toegang tot locatie op de achtergrond vereist',
+ prompt: 'Sta achtergrondlocatietoegang toe in de instellingen van je apparaat (de optie “Altijd toestaan”) om het bijhouden van de GPS-afstand te starten.',
+ },
+ preciseLocationRequiredModal: {title: 'Precieze locatie vereist', prompt: 'Schakel "precieze locatie" in de instellingen van je apparaat in om GPS-afstandsregistratie te starten.'},
desktop: {title: 'Volg afstand op je telefoon', subtitle: 'Leg kilometers of mijlen automatisch vast met GPS en zet ritten direct om in uitgaven.', button: 'Download de app'},
},
};
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index a60e02d7c5023..744dd8b0377a4 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -7906,6 +7906,20 @@ Oto *paragon testowy*, który pokazuje, jak to działa:`,
confirm: 'Odrzuć śledzenie dystansu',
},
zeroDistanceTripModal: {title: 'Nie można utworzyć wydatku', prompt: 'Nie możesz utworzyć wydatku z tym samym miejscem początkowym i końcowym.'},
+
+ locationRequiredModal: {
+ title: 'Wymagany dostęp do lokalizacji',
+ prompt: 'Aby rozpocząć śledzenie dystansu GPS, zezwól na dostęp do lokalizacji w ustawieniach swojego urządzenia.',
+ allow: 'Zezwól',
+ },
+ androidBackgroundLocationRequiredModal: {
+ title: 'Wymagany dostęp do lokalizacji w tle',
+ prompt: 'Zezwól aplikacji na dostęp do lokalizacji w tle w ustawieniach urządzenia (opcja „Zawsze zezwalaj”), aby rozpocząć śledzenie dystansu GPS.',
+ },
+ preciseLocationRequiredModal: {
+ title: 'Wymagane dokładne położenie',
+ prompt: 'Włącz proszę „dokładną lokalizację” w ustawieniach swojego urządzenia, aby rozpocząć śledzenie dystansu GPS.',
+ },
desktop: {
title: 'Śledź dystans na swoim telefonie',
subtitle: 'Automatycznie rejestruj mile lub kilometry za pomocą GPS i natychmiast zamieniaj podróże w wydatki.',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 5c6d81a4e554b..5c7a34ea947cb 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -7917,6 +7917,20 @@ Aqui está um *recibo de teste* para mostrar como funciona:`,
confirm: 'Descartar rastreamento de distância',
},
zeroDistanceTripModal: {title: 'Não é possível criar a despesa', prompt: 'Você não pode criar uma despesa com o mesmo local de partida e de chegada.'},
+
+ locationRequiredModal: {
+ title: 'Acesso à localização necessário',
+ prompt: 'Permita o acesso à localização nas configurações do seu dispositivo para iniciar o rastreamento de distância por GPS.',
+ allow: 'Permitir',
+ },
+ androidBackgroundLocationRequiredModal: {
+ title: 'Acesso à localização em segundo plano necessário',
+ prompt: 'Permita o acesso à localização em segundo plano nas configurações do seu dispositivo (opção "Permitir o tempo todo") para iniciar o rastreamento de distância por GPS.',
+ },
+ preciseLocationRequiredModal: {
+ title: 'Localização precisa obrigatória',
+ prompt: 'Ative a opção "localização precisa" nas configurações do seu dispositivo para iniciar o rastreamento de distância por GPS.',
+ },
desktop: {
title: 'Controle a distância no seu telefone',
subtitle: 'Registre milhas ou quilômetros automaticamente com o GPS e transforme viagens em despesas instantaneamente.',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 1783d8af0106e..06a31dd56de42 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -7726,6 +7726,10 @@ ${reportName}
stopGpsTrackingModal: {title: '停止 GPS 追踪', prompt: '你确定吗?这将结束你当前的旅程。', cancel: '恢复追踪', confirm: '停止 GPS 追踪'},
discardDistanceTrackingModal: {title: '丢弃距离跟踪', prompt: '您确定吗?这将放弃您当前的流程,且无法撤销。', confirm: '丢弃距离跟踪'},
zeroDistanceTripModal: {title: '无法创建报销', prompt: '你不能创建起点和终点相同的报销。'},
+
+ locationRequiredModal: {title: '需要访问位置信息', prompt: '请在设备设置中允许位置访问以开始 GPS 距离跟踪。', allow: '允许'},
+ androidBackgroundLocationRequiredModal: {title: '需要后台位置访问权限', prompt: '请在设备设置中允许应用使用“始终允许”位置访问权限,以开始 GPS 距离跟踪。'},
+ preciseLocationRequiredModal: {title: '需要精确位置', prompt: '请在设备设置中启用“精确位置”以开始 GPS 距离跟踪。'},
desktop: {title: '在手机上跟踪距离', subtitle: '使用 GPS 自动记录英里或公里,并将行程即时转换为报销费用。', button: '下载应用程序'},
},
};
diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/BackgroundLocationPermissionsFlow/index.android.tsx b/src/pages/iou/request/step/IOURequestStepDistanceGPS/BackgroundLocationPermissionsFlow/index.android.tsx
new file mode 100644
index 0000000000000..0430aa6b8c087
--- /dev/null
+++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/BackgroundLocationPermissionsFlow/index.android.tsx
@@ -0,0 +1,170 @@
+import {getBackgroundPermissionsAsync, getForegroundPermissionsAsync, PermissionStatus, requestBackgroundPermissionsAsync, requestForegroundPermissionsAsync} from 'expo-location';
+import React, {useEffect, useState} from 'react';
+import {Linking} from 'react-native';
+import ConfirmModal from '@components/ConfirmModal';
+import {loadIllustration} from '@components/Icon/IllustrationLoader';
+import {useMemoizedLazyAsset} from '@hooks/useLazyAsset';
+import useLocalize from '@hooks/useLocalize';
+import type BackgroundLocationPermissionsFlowProps from './types';
+
+async function requestForegroundPermissions({
+ onSuccess,
+ onError,
+ onPreciseLocationNotGranted,
+}: {
+ onSuccess: () => Promise;
+ onPreciseLocationNotGranted: () => void;
+ onError: () => void;
+}) {
+ try {
+ const {status, android} = await requestForegroundPermissionsAsync();
+
+ if (status === PermissionStatus.GRANTED && android?.accuracy === 'fine') {
+ await onSuccess();
+ }
+
+ if (android?.accuracy !== 'fine') {
+ onPreciseLocationNotGranted();
+ }
+ } catch (e) {
+ console.error('[GPS distance request] Failed to request foreground location permissions: ', e);
+ onError();
+ }
+}
+
+async function requestBackgroundPermissions(onGrant: () => void, onError: () => void) {
+ try {
+ const {status} = await requestBackgroundPermissionsAsync();
+
+ if (status === PermissionStatus.GRANTED) {
+ onGrant();
+ }
+ } catch (e) {
+ console.error('[GPS distance request] Failed to request background location permissions: ', e);
+ onError();
+ }
+}
+
+async function checkPermissions({
+ onGrant,
+ onDeny,
+ onAskForPermissions,
+ onError,
+}: Pick & {onAskForPermissions: () => void; onError: () => void}) {
+ try {
+ const {granted, canAskAgain, android} = await getForegroundPermissionsAsync();
+
+ if ((!granted || android?.accuracy !== 'fine') && !canAskAgain) {
+ onDeny();
+ return;
+ }
+
+ const {granted: bgGranted, canAskAgain: bgCanAskAgain} = await getBackgroundPermissionsAsync();
+
+ if (!bgGranted && !bgCanAskAgain) {
+ onDeny();
+ return;
+ }
+
+ if (granted && bgGranted && android?.accuracy === 'fine') {
+ onGrant();
+ return;
+ }
+
+ onAskForPermissions();
+ } catch (e) {
+ console.error('[GPS distance request] Failed to get location permissions: ', e);
+ onError();
+ }
+}
+
+function BackgroundLocationPermissionsFlow({startPermissionsFlow, setStartPermissionsFlow, onGrant, onDeny, onError}: BackgroundLocationPermissionsFlowProps) {
+ const [showFirstAskModal, setShowFirstAskModal] = useState(false);
+ const [showBgPermissionsModal, setShowBgPermissionsModal] = useState(false);
+ const [showPreciseLocationModal, setShowPreciseLocationModal] = useState(false);
+ const {asset: ReceiptLocationMarker} = useMemoizedLazyAsset(() => loadIllustration('ReceiptLocationMarker'));
+ const {translate} = useLocalize();
+
+ const onForegroundPermissionsGranted = async () => {
+ const {granted} = await getBackgroundPermissionsAsync();
+
+ // possible when foreground location permissions request was to grant precise location and
+ // bg permissions were already granted
+ if (granted) {
+ onGrant();
+ return;
+ }
+
+ setShowBgPermissionsModal(true);
+ };
+
+ useEffect(() => {
+ if (!startPermissionsFlow) {
+ return;
+ }
+
+ checkPermissions({onGrant, onDeny, onError, onAskForPermissions: () => setShowFirstAskModal(true)});
+ setStartPermissionsFlow(false);
+ }, [startPermissionsFlow, onGrant, onDeny, setStartPermissionsFlow, onError]);
+
+ return (
+ <>
+ setShowFirstAskModal(false)}
+ onConfirm={() => {
+ setShowFirstAskModal(false);
+ requestForegroundPermissions({onSuccess: onForegroundPermissionsGranted, onError, onPreciseLocationNotGranted: () => setShowPreciseLocationModal(true)});
+ }}
+ confirmText={translate('gps.locationRequiredModal.allow')}
+ cancelText={translate('common.dismiss')}
+ prompt={translate('gps.locationRequiredModal.prompt')}
+ iconSource={ReceiptLocationMarker}
+ iconFill={false}
+ iconWidth={140}
+ iconHeight={120}
+ shouldCenterIcon
+ shouldReverseStackedButtons
+ />
+ setShowBgPermissionsModal(false)}
+ onConfirm={() => {
+ setShowBgPermissionsModal(false);
+ requestBackgroundPermissions(onGrant, onError);
+ }}
+ confirmText={translate('common.settings')}
+ cancelText={translate('common.dismiss')}
+ prompt={translate('gps.androidBackgroundLocationRequiredModal.prompt')}
+ iconSource={ReceiptLocationMarker}
+ iconFill={false}
+ iconWidth={140}
+ iconHeight={120}
+ shouldCenterIcon
+ shouldReverseStackedButtons
+ />
+ {
+ setShowPreciseLocationModal(false);
+ Linking.openSettings();
+ }}
+ onCancel={() => setShowPreciseLocationModal(false)}
+ confirmText={translate('common.settings')}
+ cancelText={translate('common.dismiss')}
+ prompt={translate('gps.preciseLocationRequiredModal.prompt')}
+ iconSource={ReceiptLocationMarker}
+ iconFill={false}
+ iconWidth={140}
+ iconHeight={120}
+ shouldCenterIcon
+ shouldReverseStackedButtons
+ />
+ >
+ );
+}
+
+export default BackgroundLocationPermissionsFlow;
diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/BackgroundLocationPermissionsFlow/index.ios.tsx b/src/pages/iou/request/step/IOURequestStepDistanceGPS/BackgroundLocationPermissionsFlow/index.ios.tsx
new file mode 100644
index 0000000000000..589f7a11bd0d4
--- /dev/null
+++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/BackgroundLocationPermissionsFlow/index.ios.tsx
@@ -0,0 +1,159 @@
+import {getBackgroundPermissionsAsync, getForegroundPermissionsAsync, PermissionStatus, requestBackgroundPermissionsAsync, requestForegroundPermissionsAsync} from 'expo-location';
+import React, {useEffect, useRef, useState} from 'react';
+import {Linking} from 'react-native';
+import {checkLocationAccuracy} from 'react-native-permissions';
+import ConfirmModal from '@components/ConfirmModal';
+import {loadIllustration} from '@components/Icon/IllustrationLoader';
+import {useMemoizedLazyAsset} from '@hooks/useLazyAsset';
+import useLocalize from '@hooks/useLocalize';
+import type BackgroundLocationPermissionsFlowProps from './types';
+
+async function requestPermissions({
+ onGrant,
+ onError,
+ onPreciseLocationNotGranted,
+}: Pick & {onPreciseLocationNotGranted: () => void; onError: () => void}) {
+ try {
+ const {status: fgStatus} = await requestForegroundPermissionsAsync();
+
+ if (fgStatus !== PermissionStatus.GRANTED) {
+ return;
+ }
+
+ const {status} = await requestBackgroundPermissionsAsync();
+
+ if (status !== PermissionStatus.GRANTED) {
+ return;
+ }
+
+ const accuracy = await checkLocationAccuracy();
+
+ if (accuracy === 'full') {
+ onGrant();
+ return;
+ }
+
+ onPreciseLocationNotGranted();
+ } catch (e) {
+ console.error('[GPS distance request] Failed to request location permissions: ', e);
+ onError();
+ }
+}
+
+async function checkPermissions({
+ onGrant,
+ onDeny,
+ onAskForPermissions,
+ onPreciseLocationNotGranted,
+ onError,
+}: Pick & {onAskForPermissions: () => void; onPreciseLocationNotGranted: () => void; onError: () => void}) {
+ try {
+ const {granted, canAskAgain} = await getForegroundPermissionsAsync();
+
+ if (!canAskAgain && !granted) {
+ onDeny();
+ return;
+ }
+
+ const {granted: bgGranted, canAskAgain: bgCanAskAgain} = await getBackgroundPermissionsAsync();
+
+ if (!bgCanAskAgain && !bgGranted) {
+ onDeny();
+ return;
+ }
+
+ if (granted && bgGranted) {
+ const accuracy = await checkLocationAccuracy();
+
+ if (accuracy === 'full') {
+ onGrant();
+ return;
+ }
+
+ onPreciseLocationNotGranted();
+ return;
+ }
+
+ onAskForPermissions();
+ } catch (e) {
+ console.error('[GPS distance request] Failed to get location permissions: ', e);
+ onError();
+ }
+}
+
+function BackgroundLocationPermissionsFlow({startPermissionsFlow, setStartPermissionsFlow, onError, onGrant, onDeny}: BackgroundLocationPermissionsFlowProps) {
+ const [showFirstAskModal, setShowFirstAskModal] = useState(false);
+ const [showPreciseLocationModal, setShowPreciseLocationModal] = useState(false);
+ const {asset: ReceiptLocationMarker} = useMemoizedLazyAsset(() => loadIllustration('ReceiptLocationMarker'));
+ const {translate} = useLocalize();
+
+ const onModalHide = useRef<(() => void) | null>(null);
+
+ useEffect(() => {
+ if (!startPermissionsFlow) {
+ return;
+ }
+
+ checkPermissions({onGrant, onDeny, onError, onAskForPermissions: () => setShowFirstAskModal(true), onPreciseLocationNotGranted: () => setShowPreciseLocationModal(true)});
+ setStartPermissionsFlow(false);
+ }, [startPermissionsFlow, onDeny, onGrant, setStartPermissionsFlow, onError]);
+
+ const requestPermissionsFirstAsk = () => {
+ setShowFirstAskModal(false);
+ requestPermissions({
+ onGrant,
+ onError,
+ onPreciseLocationNotGranted: () => {
+ // can't trigger Precise Location modal before First Ask modal hides
+ // as the animations clash and Precise Location modal doesn't show on iOS
+ onModalHide.current = () => setShowPreciseLocationModal(true);
+ },
+ });
+ };
+
+ return (
+ <>
+ {
+ setShowFirstAskModal(false);
+ }}
+ confirmText={translate('gps.locationRequiredModal.allow')}
+ cancelText={translate('common.dismiss')}
+ prompt={translate('gps.locationRequiredModal.prompt')}
+ iconSource={ReceiptLocationMarker}
+ iconFill={false}
+ iconWidth={140}
+ iconHeight={120}
+ shouldCenterIcon
+ shouldReverseStackedButtons
+ onModalHide={() => {
+ onModalHide.current?.();
+ onModalHide.current = null;
+ }}
+ />
+ {
+ setShowPreciseLocationModal(false);
+ Linking.openSettings();
+ }}
+ onCancel={() => setShowPreciseLocationModal(false)}
+ confirmText={translate('common.settings')}
+ cancelText={translate('common.dismiss')}
+ prompt={translate('gps.preciseLocationRequiredModal.prompt')}
+ iconSource={ReceiptLocationMarker}
+ iconFill={false}
+ iconWidth={140}
+ iconHeight={120}
+ shouldCenterIcon
+ shouldReverseStackedButtons
+ />
+ >
+ );
+}
+
+export default BackgroundLocationPermissionsFlow;
diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/BackgroundLocationPermissionsFlow/index.tsx b/src/pages/iou/request/step/IOURequestStepDistanceGPS/BackgroundLocationPermissionsFlow/index.tsx
new file mode 100644
index 0000000000000..62602c022dd8a
--- /dev/null
+++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/BackgroundLocationPermissionsFlow/index.tsx
@@ -0,0 +1,8 @@
+import type BackgroundLocationPermissionsFlowProps from './types';
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+function BackgroundLocationPermissionsFlow(props: BackgroundLocationPermissionsFlowProps) {
+ return null;
+}
+
+export default BackgroundLocationPermissionsFlow;
diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/BackgroundLocationPermissionsFlow/types.ts b/src/pages/iou/request/step/IOURequestStepDistanceGPS/BackgroundLocationPermissionsFlow/types.ts
new file mode 100644
index 0000000000000..5238e177d7bd6
--- /dev/null
+++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/BackgroundLocationPermissionsFlow/types.ts
@@ -0,0 +1,9 @@
+type BackgroundLocationPermissionsFlowProps = {
+ startPermissionsFlow: boolean;
+ setStartPermissionsFlow: React.Dispatch>;
+ onError: () => void;
+ onGrant: () => void;
+ onDeny: () => void;
+};
+
+export default BackgroundLocationPermissionsFlowProps;
diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSButtons/index.tsx b/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSButtons/index.tsx
index 715ef46cceae2..efabc682052e4 100644
--- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSButtons/index.tsx
+++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSButtons/index.tsx
@@ -1,11 +1,14 @@
import React, {useState} from 'react';
-import {View} from 'react-native';
+import {Linking, View} from 'react-native';
import Button from '@components/Button';
import ConfirmModal from '@components/ConfirmModal';
+import {loadIllustration} from '@components/Icon/IllustrationLoader';
+import {useMemoizedLazyAsset} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useThemeStyles from '@hooks/useThemeStyles';
import {addGpsPoints, initGpsDraft, resetGPSDraftDetails, setEndAddress, setIsTracking, setStartAddress} from '@libs/actions/GPSDraftDetails';
+import BackgroundLocationPermissionsFlow from '@pages/iou/request/step/IOURequestStepDistanceGPS/BackgroundLocationPermissionsFlow';
import ONYXKEYS from '@src/ONYXKEYS';
type ButtonsProps = {
@@ -17,10 +20,13 @@ type ButtonsProps = {
// next line will be removed in a follow-up PR where the currently unused props will be used
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function GPSButtons({navigateToNextStep, setShouldShowStartError, setShouldShowPermissionsError}: ButtonsProps) {
+ const [startPermissionsFlow, setStartPermissionsFlow] = useState(false);
const [showDiscardConfirmation, setShowDiscardConfirmation] = useState(false);
const [showStopConfirmation, setShowStopConfirmation] = useState(false);
const [showZeroDistanceModal, setShowZeroDistanceModal] = useState(false);
+ const [showLocationRequiredModal, setShowLocationRequiredModal] = useState(false);
+ const {asset: ReceiptLocationMarker} = useMemoizedLazyAsset(() => loadIllustration('ReceiptLocationMarker'));
const [gpsDraftDetails] = useOnyx(ONYXKEYS.GPS_DRAFT_DETAILS, {canBeMissing: true});
const styles = useThemeStyles();
const {translate} = useLocalize();
@@ -28,8 +34,8 @@ function GPSButtons({navigateToNextStep, setShouldShowStartError, setShouldShowP
const isTripCaptured = !gpsDraftDetails?.isTracking && (gpsDraftDetails?.gpsPoints?.length ?? 0) > 0;
/**
- * todo: startGpsTrip, onNext and stopGpsTrip are implemented like this to show all UI components to test as of now,
- * their proper implementation will be added in follow-up PRs
+ * todo: startGpsTrip, onNext, checkPermissions and stopGpsTrip are implemented like this to show
+ * all UI components to test as of now, their proper implementation will be added in follow-up PRs
*/
const startGpsTrip = () => {
initGpsDraft();
@@ -55,6 +61,10 @@ function GPSButtons({navigateToNextStep, setShouldShowStartError, setShouldShowP
navigateToNextStep();
};
+ const checkPermissions = () => {
+ setStartPermissionsFlow(true);
+ };
+
return (
<>
{isTripCaptured ? (
@@ -79,7 +89,7 @@ function GPSButtons({navigateToNextStep, setShouldShowStartError, setShouldShowP
) : (