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 ) : (