diff --git a/ios/LiveActivity/GpsTripAttributes.swift b/ios/LiveActivity/GpsTripAttributes.swift index cd511d4d6ce8b..6e863e85fb4c8 100644 --- a/ios/LiveActivity/GpsTripAttributes.swift +++ b/ios/LiveActivity/GpsTripAttributes.swift @@ -2,13 +2,12 @@ import ActivityKit struct GpsTripAttributes: ActivityAttributes { var deepLink: String - var buttonText: String - var subtitle: String - public struct ContentState: Codable, Hashable { var distance: Double var distanceUnit: String var distanceUnitLong: String + var buttonText: String + var subtitle: String } } diff --git a/ios/LiveActivity/GpsTripLiveActivity.swift b/ios/LiveActivity/GpsTripLiveActivity.swift index a52374073bf9b..10d9e2ab353b1 100644 --- a/ios/LiveActivity/GpsTripLiveActivity.swift +++ b/ios/LiveActivity/GpsTripLiveActivity.swift @@ -24,7 +24,7 @@ struct GpsTripLiveActivity: Widget { .resizable() .scaledToFit() .frame(maxWidth: 64) - Text(context.attributes.subtitle) + Text(context.state.subtitle) .font(.custom("ExpensifyNeue-Regular", size: 15)) .foregroundColor(.white) } @@ -61,7 +61,7 @@ struct GpsTripLiveActivity: Widget { .resizable() .scaledToFit() .frame(maxWidth: 64) - Text(context.attributes.subtitle) + Text(context.state.subtitle) .font(.custom("ExpensifyNeue-Regular", size: 15)) .foregroundColor(.white) .lineLimit(1) @@ -83,7 +83,7 @@ struct GpsTripLiveActivity: Widget { .frame(minHeight: 70, maxHeight: .infinity, alignment: .bottom) } DynamicIslandExpandedRegion(.bottom) { - Text(context.attributes.buttonText) + Text(context.state.buttonText) .font(.custom("ExpensifyNeue-Bold", size: 15)) .foregroundColor(.expensifyGreen) .frame(maxWidth: .infinity) diff --git a/src/components/GPSTripStateChecker/index.native.tsx b/src/components/GPSTripStateChecker/index.native.tsx index bcbf26b6a8850..3b468dbaa87f7 100644 --- a/src/components/GPSTripStateChecker/index.native.tsx +++ b/src/components/GPSTripStateChecker/index.native.tsx @@ -14,7 +14,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import {useSplashScreenState} from '@src/SplashScreenStateContext'; -import useUpdateGpsNotificationOnUnitChange from './useUpdateGpsNotificationOnUnitChange'; +import useUpdateGpsNotification from './useUpdateGpsNotification'; import useUpdateGpsTripOnReconnect from './useUpdateGpsTripOnReconnect'; function GPSTripStateChecker() { @@ -28,7 +28,7 @@ function GPSTripStateChecker() { const reportID = gpsDraftDetails?.reportID ?? generateReportID(); useUpdateGpsTripOnReconnect(); - useUpdateGpsNotificationOnUnitChange(); + useUpdateGpsNotification(); useEffect(() => { async function handleGpsTripInProgressOnAppRestart() { diff --git a/src/components/GPSTripStateChecker/useUpdateGpsNotificationOnUnitChange/index.ios.ts b/src/components/GPSTripStateChecker/useUpdateGpsNotification/index.ios.ts similarity index 59% rename from src/components/GPSTripStateChecker/useUpdateGpsNotificationOnUnitChange/index.ios.ts rename to src/components/GPSTripStateChecker/useUpdateGpsNotification/index.ios.ts index 8e3341fffb5ec..9dc2c10d8cbda 100644 --- a/src/components/GPSTripStateChecker/useUpdateGpsNotificationOnUnitChange/index.ios.ts +++ b/src/components/GPSTripStateChecker/useUpdateGpsNotification/index.ios.ts @@ -1,13 +1,37 @@ -import {useEffect} from 'react'; +import {useEffect, useRef} from 'react'; import useDefaultExpensePolicy from '@hooks/useDefaultExpensePolicy'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import shouldUseDefaultExpensePolicyUtil from '@libs/shouldUseDefaultExpensePolicy'; -import {shouldUpdateGpsNotificationUnit, updateGpsTripNotificationUnit} from '@pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications'; +import {shouldUpdateGpsNotificationUnit, updateGpsTripNotificationLanguage, updateGpsTripNotificationUnit} from '@pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +function useUpdateGpsNotification() { + useUpdateGpsNotificationOnLanguageChange(); + useUpdateGpsNotificationOnUnitChange(); +} + +function useUpdateGpsNotificationOnLanguageChange() { + const {translate, preferredLocale} = useLocalize(); + const currentPreferredLocale = useRef(preferredLocale); + + useEffect(() => { + if (currentPreferredLocale.current === preferredLocale) { + return; + } + + currentPreferredLocale.current = preferredLocale; + + if (!shouldUpdateGpsNotificationUnit()) { + return; + } + + updateGpsTripNotificationLanguage(translate); + }, [preferredLocale, translate]); +} + function useUpdateGpsNotificationOnUnitChange() { const {translate} = useLocalize(); @@ -28,4 +52,4 @@ function useUpdateGpsNotificationOnUnitChange() { }, [unit, translate]); } -export default useUpdateGpsNotificationOnUnitChange; +export default useUpdateGpsNotification; diff --git a/src/components/GPSTripStateChecker/useUpdateGpsNotification/index.ts b/src/components/GPSTripStateChecker/useUpdateGpsNotification/index.ts new file mode 100644 index 0000000000000..1b190c1771866 --- /dev/null +++ b/src/components/GPSTripStateChecker/useUpdateGpsNotification/index.ts @@ -0,0 +1,4 @@ +// GPS notifications updates are iOS only +function useUpdateGpsNotification() {} + +export default useUpdateGpsNotification; diff --git a/src/components/GPSTripStateChecker/useUpdateGpsNotificationOnUnitChange/index.ts b/src/components/GPSTripStateChecker/useUpdateGpsNotificationOnUnitChange/index.ts deleted file mode 100644 index 1fdb33ff8ebb2..0000000000000 --- a/src/components/GPSTripStateChecker/useUpdateGpsNotificationOnUnitChange/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -// Only iOS needs notification updates on unit change, as Android GPS notification doesn't -// have unit-dependent data -function useUpdateGpsNotificationOnUnitChange() {} - -export default useUpdateGpsNotificationOnUnitChange; diff --git a/src/languages/de.ts b/src/languages/de.ts index 77d48f1d5c4a1..8ad50ae843b61 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -362,6 +362,8 @@ const translations: TranslationDeepObject = { per: 'pro', mi: 'Meile', km: 'Kilometer', + milesAbbreviated: 'mi', + kilometersAbbreviated: 'km', copied: 'Kopiert!', someone: 'Jemand', total: 'Gesamt', diff --git a/src/languages/en.ts b/src/languages/en.ts index 10572ed7654ca..c6b4d9a44efe5 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -372,6 +372,8 @@ const translations = { // @context Unit label for “mile.” Should be treated as a measurement unit and may require capitalization depending on locale conventions. mi: 'mile', km: 'kilometer', + milesAbbreviated: 'mi', + kilometersAbbreviated: 'km', copied: 'Copied!', someone: 'Someone', total: 'Total', diff --git a/src/languages/es.ts b/src/languages/es.ts index 8cb12051ca9d0..d1c38e1c492db 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -235,6 +235,8 @@ const translations: TranslationDeepObject = { per: 'por', mi: 'milla', km: 'kilómetro', + milesAbbreviated: 'mi', + kilometersAbbreviated: 'km', copied: '¡Copiado!', someone: 'Alguien', total: 'Total', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 598a4ba2d2948..23344a551e7c9 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -362,6 +362,8 @@ const translations: TranslationDeepObject = { per: 'par', mi: 'mile', km: 'kilomètre', + milesAbbreviated: 'mi', + kilometersAbbreviated: 'km', copied: 'Copié !', someone: 'Quelqu’un', total: 'Total', diff --git a/src/languages/it.ts b/src/languages/it.ts index c45b87bc6e953..6644029449a4a 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -362,6 +362,8 @@ const translations: TranslationDeepObject = { per: 'for', mi: 'miglio', km: 'chilometro', + milesAbbreviated: 'mi', + kilometersAbbreviated: 'km', copied: 'Copiato!', someone: 'Qualcuno', total: 'Totale', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 71e7d9c67d9fa..9a537b2dced3a 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -362,6 +362,8 @@ const translations: TranslationDeepObject = { per: 'あたり', mi: 'マイル', km: 'キロメートル', + milesAbbreviated: 'マイル', + kilometersAbbreviated: 'キロ', copied: 'コピーしました!', someone: '誰か', total: '合計', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index cb4452c17a1d7..513b1449976fe 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -362,6 +362,8 @@ const translations: TranslationDeepObject = { per: 'per', mi: 'mijl', km: 'kilometer', + milesAbbreviated: 'mi', + kilometersAbbreviated: 'km', copied: 'Gekopieerd!', someone: 'Iemand', total: 'Totaal', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 8c67ed2df379e..d059b8dc3d013 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -362,6 +362,8 @@ const translations: TranslationDeepObject = { per: 'na', mi: 'mila', km: 'kilometr', + milesAbbreviated: 'mi', + kilometersAbbreviated: 'km', copied: 'Skopiowano!', someone: 'Ktoś', total: 'Suma', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 69cdea209e08e..94035ecded153 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -362,6 +362,8 @@ const translations: TranslationDeepObject = { per: 'por', mi: 'milha', km: 'quilômetro', + milesAbbreviated: 'mi', + kilometersAbbreviated: 'km', copied: 'Copiado!', someone: 'Alguém', total: 'Total', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 1e6ac56947347..0ed15d56690f3 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -362,6 +362,8 @@ const translations: TranslationDeepObject = { per: '每', mi: '英里', km: '千米', + milesAbbreviated: '英里', + kilometersAbbreviated: '千米', copied: '已复制!', someone: '某人', total: '总计', diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications/index.android.ts b/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications/index.android.ts index 6b96b7af27e99..fa26b1e390f7b 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications/index.android.ts +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications/index.android.ts @@ -52,4 +52,15 @@ function updateGpsTripNotificationDistance(_distanceInMeters: number) {} // eslint-disable-next-line @typescript-eslint/no-unused-vars function updateGpsTripNotificationUnit(_translate: LocalizedTranslate, _unit: Unit) {} -export {startGpsTripNotification, stopGpsTripNotification, updateGpsTripNotificationDistance, updateGpsTripNotificationUnit, checkAndCleanGpsNotification, shouldUpdateGpsNotificationUnit}; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function updateGpsTripNotificationLanguage(_translate: LocalizedTranslate) {} + +export { + startGpsTripNotification, + stopGpsTripNotification, + updateGpsTripNotificationDistance, + updateGpsTripNotificationUnit, + updateGpsTripNotificationLanguage, + checkAndCleanGpsNotification, + shouldUpdateGpsNotificationUnit, +}; diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications/index.ios.ts b/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications/index.ios.ts index 36a9ac7659e3b..8c1c07976a4c6 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications/index.ios.ts +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications/index.ios.ts @@ -1,4 +1,5 @@ import Airship from '@ua/react-native-airship'; +import type {JsonObject} from '@ua/react-native-airship'; import type {LocalizedTranslate} from '@components/LocaleContextProvider'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import CONST from '@src/CONST'; @@ -8,21 +9,45 @@ import type {Unit} from '@src/types/onyx/Policy'; const ATTRIBUTES_TYPE = 'GpsTripAttributes'; let activityId: string | null = null; -let lastDistanceUnit: Unit | null = null; -let lastDistanceUnitLong: string | null = null; -let lastDistanceInMeters: number | null = null; -function getDistanceUnitLong(translate: LocalizedTranslate, unit: Unit) { +type GpsLiveActivityState = { + distanceUnit: Unit; + distanceUnitFull: string; + distanceUnitAbbreviated: string; + buttonText: string; + subtitle: string; + distanceInMeters: number; +}; + +let liveActivityState: GpsLiveActivityState | null = null; + +function getDistanceUnitFull(translate: LocalizedTranslate, unit: Unit) { return unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.miles') : translate('common.kilometers'); } -function startGpsTripNotification(translate: LocalizedTranslate, reportID: string, unit: Unit, distanceInMeters = 0) { - const subtitle = translate('gps.liveActivity.subtitle'); - const buttonText = translate('gps.liveActivity.button'); +function getDistanceUnitAbbreviated(translate: LocalizedTranslate, unit: Unit) { + return unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.milesAbbreviated') : translate('common.kilometersAbbreviated'); +} - lastDistanceUnitLong = getDistanceUnitLong(translate, unit); - lastDistanceUnit = unit; - lastDistanceInMeters = distanceInMeters; +function getLiveActivityUpdateState(distance: number, state: GpsLiveActivityState): JsonObject { + return { + distance, + distanceUnit: state.distanceUnitAbbreviated, + distanceUnitLong: state.distanceUnitFull, + subtitle: state.subtitle, + buttonText: state.buttonText, + }; +} + +function startGpsTripNotification(translate: LocalizedTranslate, reportID: string, unit: Unit, distanceInMeters = 0) { + liveActivityState = { + subtitle: translate('gps.liveActivity.subtitle'), + buttonText: translate('gps.liveActivity.button'), + distanceUnit: unit, + distanceUnitFull: getDistanceUnitFull(translate, unit), + distanceUnitAbbreviated: getDistanceUnitAbbreviated(translate, unit), + distanceInMeters, + }; const deepLink = ROUTES.DISTANCE_REQUEST_CREATE_TAB_GPS.getRoute(CONST.IOU.ACTION.CREATE, CONST.IOU.TYPE.CREATE, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, reportID); @@ -32,10 +57,10 @@ function startGpsTripNotification(translate: LocalizedTranslate, reportID: strin .start({ attributesType: ATTRIBUTES_TYPE, content: { - state: {distance, distanceUnit: unit, distanceUnitLong: lastDistanceUnitLong}, + state: getLiveActivityUpdateState(distance, liveActivityState), relevanceScore: 100, }, - attributes: {deepLink, subtitle, buttonText}, + attributes: {deepLink}, }) .then((activity) => { activityId = activity.id; @@ -43,13 +68,13 @@ function startGpsTripNotification(translate: LocalizedTranslate, reportID: strin .catch((error: unknown) => console.error('[GPS Live Activity] Failed to start', error)); } -function updateGpsTripNotification(nonNullActivityId: string, distance: number, distanceUnit: Unit, distanceUnitLong: string) { +function updateGpsTripNotification(nonNullActivityId: string, distance: number, state: GpsLiveActivityState) { Airship.iOS.liveActivityManager .update({ attributesType: ATTRIBUTES_TYPE, activityId: nonNullActivityId, content: { - state: {distance, distanceUnit, distanceUnitLong}, + state: getLiveActivityUpdateState(distance, state), relevanceScore: 100, }, }) @@ -57,35 +82,54 @@ function updateGpsTripNotification(nonNullActivityId: string, distance: number, } function updateGpsTripNotificationDistance(distanceInMeters: number) { - if (activityId === null || lastDistanceUnit === null || lastDistanceUnitLong === null) { - console.error('[GPS Live Activity] Failed to start update: activityId or lastDistanceUnit/lastDistanceUnitLong is null'); + if (activityId === null || liveActivityState === null) { + console.error('[GPS Live Activity] Failed to start update: activityId or liveActivityState is null'); return; } - lastDistanceInMeters = distanceInMeters; + liveActivityState.distanceInMeters = distanceInMeters; - const distance = DistanceRequestUtils.convertDistanceUnit(distanceInMeters, lastDistanceUnit); + const distance = DistanceRequestUtils.convertDistanceUnit(distanceInMeters, liveActivityState.distanceUnit); - updateGpsTripNotification(activityId, distance, lastDistanceUnit, lastDistanceUnitLong); + updateGpsTripNotification(activityId, distance, liveActivityState); } function updateGpsTripNotificationUnit(translate: LocalizedTranslate, unit: Unit) { - if (activityId === null || lastDistanceInMeters === null) { - console.error('[GPS Live Activity] Failed to start update: activityId or lastDistanceInMeters is null'); + if (activityId === null || liveActivityState === null) { + console.error('[GPS Live Activity] Failed to start update: activityId or liveActivityState is null'); return; } // Update is not needed if the distance unit will stay the same - if (lastDistanceUnit === unit) { + if (liveActivityState.distanceUnit === unit) { return; } - lastDistanceUnitLong = getDistanceUnitLong(translate, unit); - lastDistanceUnit = unit; + liveActivityState.distanceUnit = unit; + liveActivityState.distanceUnitAbbreviated = getDistanceUnitAbbreviated(translate, unit); + liveActivityState.distanceUnitFull = getDistanceUnitFull(translate, unit); + + const distance = DistanceRequestUtils.convertDistanceUnit(liveActivityState.distanceInMeters, unit); + + updateGpsTripNotification(activityId, distance, liveActivityState); +} + +function updateGpsTripNotificationLanguage(translate: LocalizedTranslate) { + if (activityId === null || liveActivityState === null) { + console.error('[GPS Live Activity] Failed to start update: activityId or liveActivityState is null'); + return; + } + + const unit = liveActivityState.distanceUnit; + + liveActivityState.distanceUnitAbbreviated = getDistanceUnitAbbreviated(translate, unit); + liveActivityState.distanceUnitFull = getDistanceUnitFull(translate, unit); + liveActivityState.subtitle = translate('gps.liveActivity.subtitle'); + liveActivityState.buttonText = translate('gps.liveActivity.button'); - const distance = DistanceRequestUtils.convertDistanceUnit(lastDistanceInMeters, unit); + const distance = DistanceRequestUtils.convertDistanceUnit(liveActivityState.distanceInMeters, unit); - updateGpsTripNotification(activityId, distance, lastDistanceUnit, lastDistanceUnitLong); + updateGpsTripNotification(activityId, distance, liveActivityState); } function stopGpsTripNotification() { @@ -102,9 +146,7 @@ function stopGpsTripNotification() { .catch((error: unknown) => console.error('[GPS Live Activity] Failed to end', error)); activityId = null; - lastDistanceUnit = null; - lastDistanceInMeters = null; - lastDistanceUnitLong = null; + liveActivityState = null; } async function checkAndCleanGpsNotification() { @@ -129,4 +171,12 @@ function shouldUpdateGpsNotificationUnit() { return activityId !== null; } -export {startGpsTripNotification, updateGpsTripNotificationDistance, updateGpsTripNotificationUnit, stopGpsTripNotification, checkAndCleanGpsNotification, shouldUpdateGpsNotificationUnit}; +export { + startGpsTripNotification, + updateGpsTripNotificationDistance, + updateGpsTripNotificationUnit, + updateGpsTripNotificationLanguage, + stopGpsTripNotification, + checkAndCleanGpsNotification, + shouldUpdateGpsNotificationUnit, +}; diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications/index.ts b/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications/index.ts index 78771d082ed6d..b6fa0a53ce305 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications/index.ts +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications/index.ts @@ -13,6 +13,9 @@ function updateGpsTripNotificationDistance(_distanceInMeters: number) {} // eslint-disable-next-line @typescript-eslint/no-unused-vars function updateGpsTripNotificationUnit(_translate: LocalizedTranslate, _unit: Unit) {} +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function updateGpsTripNotificationLanguage(_translate: LocalizedTranslate) {} + async function checkAndCleanGpsNotification(): Promise { // no-op } @@ -21,4 +24,12 @@ function shouldUpdateGpsNotificationUnit(): boolean { return false; } -export {startGpsTripNotification, stopGpsTripNotification, updateGpsTripNotificationDistance, updateGpsTripNotificationUnit, checkAndCleanGpsNotification, shouldUpdateGpsNotificationUnit}; +export { + startGpsTripNotification, + stopGpsTripNotification, + updateGpsTripNotificationDistance, + updateGpsTripNotificationUnit, + updateGpsTripNotificationLanguage, + checkAndCleanGpsNotification, + shouldUpdateGpsNotificationUnit, +};