diff --git a/__mocks__/@ua/react-native-airship.ts b/__mocks__/@ua/react-native-airship.ts index 08ec1b781fc21..0f5c34559dda5 100644 --- a/__mocks__/@ua/react-native-airship.ts +++ b/__mocks__/@ua/react-native-airship.ts @@ -1,4 +1,4 @@ -import type {AirshipContact, AirshipPush, AirshipPushAndroid, AirshipPushIOS, AirshipRoot} from '@ua/react-native-airship'; +import type {AirshipContact, AirshipLiveActivityManager, AirshipPush, AirshipPushAndroid, AirshipPushIOS, AirshipRoot, AirshipRootIOS} from '@ua/react-native-airship'; // eslint-disable-next-line no-restricted-syntax enum EventType { @@ -69,11 +69,24 @@ const contact = jest.fn().mockImplementation(() => ({ module: jest.fn(), }))() as AirshipContact; +const liveActivityManager = jest.fn().mockImplementation(() => ({ + list: jest.fn(() => Promise.resolve([])), + listAll: jest.fn(() => Promise.resolve([])), + start: jest.fn(() => Promise.resolve({id: 'mock-activity-id'})), + update: jest.fn(() => Promise.resolve()), + end: jest.fn(() => Promise.resolve()), +}))() as AirshipLiveActivityManager; + +const airshipIOS = jest.fn().mockImplementation(() => ({ + liveActivityManager, +}))() as AirshipRootIOS; + const Airship: Partial = { addListener: jest.fn(), removeAllListeners: jest.fn(), push, contact, + iOS: airshipIOS, }; export default Airship; diff --git a/cspell.json b/cspell.json index f9fe50a6e5c1f..a6bc2e618037c 100644 --- a/cspell.json +++ b/cspell.json @@ -835,7 +835,8 @@ "canvaskit", "Invoicify", "UKEU", - "USCA" + "USCA", + "widgetkit" ], "ignorePaths": [ "src/languages/de.ts", diff --git a/ios/AppDelegate.swift b/ios/AppDelegate.swift index cb2e6d5b13837..e68917406303c 100644 --- a/ios/AppDelegate.swift +++ b/ios/AppDelegate.swift @@ -12,6 +12,8 @@ import ReactAppDependencyProvider import ExpoModulesCore import Firebase import Expo +import ActivityKit +import AirshipFrameworkProxy @main class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate { @@ -58,10 +60,30 @@ class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate { RNBackgroundTaskManager.setup() + // Register GPS trip Live Activity with Airship + if #available(iOS 16.1, *) { + try? LiveActivityManager.shared.setup { configurator in + await configurator.register( + forType: Activity.self, + airshipNameExtractor: nil + ) + } + } + return true } + override func applicationWillTerminate(_ application: UIApplication) { + if #available(iOS 16.2, *) { + for activity in Activity.activities { + Task.detached { + await activity.end(nil, dismissalPolicy: .immediate) + } + } + } + } + override func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { return RCTLinkingManager.application(application, open: url, options: options) } diff --git a/ios/LiveActivity/Assets.xcassets/logo.imageset/Contents.json b/ios/LiveActivity/Assets.xcassets/logo.imageset/Contents.json new file mode 100644 index 0000000000000..151248e96b219 --- /dev/null +++ b/ios/LiveActivity/Assets.xcassets/logo.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "logo.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/ios/LiveActivity/Assets.xcassets/logo.imageset/logo.svg b/ios/LiveActivity/Assets.xcassets/logo.imageset/logo.svg new file mode 100644 index 0000000000000..b1a91e36397eb --- /dev/null +++ b/ios/LiveActivity/Assets.xcassets/logo.imageset/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ios/LiveActivity/Assets.xcassets/wordmark.imageset/Contents.json b/ios/LiveActivity/Assets.xcassets/wordmark.imageset/Contents.json new file mode 100644 index 0000000000000..d42940dc14fd0 --- /dev/null +++ b/ios/LiveActivity/Assets.xcassets/wordmark.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "wordmark.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/ios/LiveActivity/Assets.xcassets/wordmark.imageset/wordmark.svg b/ios/LiveActivity/Assets.xcassets/wordmark.imageset/wordmark.svg new file mode 100644 index 0000000000000..c3f721efe2369 --- /dev/null +++ b/ios/LiveActivity/Assets.xcassets/wordmark.imageset/wordmark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ios/LiveActivity/GpsTripAttributes.swift b/ios/LiveActivity/GpsTripAttributes.swift new file mode 100644 index 0000000000000..7dffa4534d271 --- /dev/null +++ b/ios/LiveActivity/GpsTripAttributes.swift @@ -0,0 +1,13 @@ +import ActivityKit + +struct GpsTripAttributes: ActivityAttributes { + var deepLink: String + var buttonText: String + var subtitle: String + var distanceUnit: String + var distanceUnitLong: String + + public struct ContentState: Codable, Hashable { + var distance: Double + } +} diff --git a/ios/LiveActivity/GpsTripLiveActivity.swift b/ios/LiveActivity/GpsTripLiveActivity.swift new file mode 100644 index 0000000000000..745e70d9d618f --- /dev/null +++ b/ios/LiveActivity/GpsTripLiveActivity.swift @@ -0,0 +1,117 @@ +import WidgetKit +import SwiftUI + +private extension Color { + static let expensifyGreen = Color(red: 3 / 255, green: 212 / 255, blue: 124 / 255) +} + +private func distanceString(distance: Double) -> String { + String(format: "%.1f", distance) +} + +private func distanceStringShort(distance: Double, distanceUnit: String) -> String { + String(format: "%.0f\(distanceUnit)", distance) +} + +@available(iOS 16.1, *) +struct GpsTripLiveActivity: Widget { + var body: some WidgetConfiguration { + ActivityConfiguration(for: GpsTripAttributes.self) { context in + VStack { + HStack(alignment: .bottom) { + VStack(alignment: .leading, spacing: 14) { + Image("wordmark") + .resizable() + .scaledToFit() + .frame(maxWidth: 64) + Text(context.attributes.subtitle) + .font(.custom("ExpensifyNeue-Regular", size: 15)) + .foregroundColor(.white) + } + Spacer() + VStack(alignment: .trailing, spacing: -4) { + Text(distanceString(distance: context.state.distance)) + .font(.custom("ExpensifyNeue-Bold", size: 45)) + .foregroundColor(.expensifyGreen) + Text(context.attributes.distanceUnitLong) + .font(.custom("ExpensifyNeue-Regular", size: 15)) + .foregroundColor(.white) + } + } + } + .padding(.top, 22) + .padding(.bottom, 28) + .padding(.horizontal, 24) + .background { + LinearGradient( + stops: [ + .init(color: Color(.sRGB, red: 0, green: 46 / 255, blue: 34 / 255), location: 0), + .init(color: Color(.sRGB, red: 6 / 255, green: 27 / 255, blue: 9 / 255), location: 1), + ], + startPoint: UnitPoint(x: 0, y: 0.41), + endPoint: UnitPoint(x: 1, y: 0.59) + ) + } + .widgetURL(URL(string: "new-expensify://\(context.attributes.deepLink)")) + } dynamicIsland: { context in + DynamicIsland { + DynamicIslandExpandedRegion(.leading) { + VStack(alignment: .leading, spacing: 14) { + Image("wordmark") + .resizable() + .scaledToFit() + .frame(maxWidth: 64) + Text(context.attributes.subtitle) + .font(.custom("ExpensifyNeue-Regular", size: 15)) + .foregroundColor(.white) + .lineLimit(1) + .minimumScaleFactor(0.5) + } + .frame(minHeight: 70, maxHeight: .infinity, alignment: .bottom) + } + DynamicIslandExpandedRegion(.trailing) { + VStack(alignment: .trailing, spacing: -4) { + Text(distanceString(distance: context.state.distance)) + .font(.custom("ExpensifyNeue-Bold", size: 45)) + .foregroundColor(.expensifyGreen) + .lineLimit(1) + .minimumScaleFactor(0.3) + Text(context.attributes.distanceUnitLong) + .font(.custom("ExpensifyNeue-Regular", size: 15)) + .foregroundColor(.white) + } + .frame(minHeight: 70, maxHeight: .infinity, alignment: .bottom) + } + DynamicIslandExpandedRegion(.bottom) { + Text(context.attributes.buttonText) + .font(.custom("ExpensifyNeue-Bold", size: 15)) + .foregroundColor(.expensifyGreen) + .frame(maxWidth: .infinity) + .padding(.vertical, 10) + .background(Color.expensifyGreen.opacity(0.2)) + .clipShape(Capsule()) + .padding(.top, 10) + } + } compactLeading: { + Image("logo") + .resizable() + .scaledToFit() + .frame(maxWidth: 23, maxHeight: 23) + .clipShape(RoundedRectangle(cornerRadius: 7)) + } compactTrailing: { + Text(distanceStringShort(distance: context.state.distance, distanceUnit: context.attributes.distanceUnit)) + .font(.custom("ExpensifyNeue-Bold", size: 15)) + .foregroundColor(.white) + .minimumScaleFactor(0.5) + } minimal: { + Image("logo") + .resizable() + .scaledToFit() + .frame(maxWidth: 23, maxHeight: 23) + .clipShape(RoundedRectangle(cornerRadius: 7)) + + } + .widgetURL(URL(string: "new-expensify://\(context.attributes.deepLink)")) + } + } +} diff --git a/ios/LiveActivity/GpsTripWidgetBundle.swift b/ios/LiveActivity/GpsTripWidgetBundle.swift new file mode 100644 index 0000000000000..fac337bddc883 --- /dev/null +++ b/ios/LiveActivity/GpsTripWidgetBundle.swift @@ -0,0 +1,11 @@ +import WidgetKit +import SwiftUI + +@main +struct GpsTripWidgetBundle: WidgetBundle { + var body: some Widget { + if #available(iOS 16.1, *) { + GpsTripLiveActivity() + } + } +} diff --git a/ios/LiveActivity/Info.plist b/ios/LiveActivity/Info.plist new file mode 100644 index 0000000000000..76338e43019d1 --- /dev/null +++ b/ios/LiveActivity/Info.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDisplayName + $(PRODUCT_NAME) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleName + $(PRODUCT_NAME) + CFBundleShortVersionString + 9.3.26 + CFBundleVersion + 9.3.26.4 + NSExtension + + NSExtensionPointIdentifier + com.apple.widgetkit-extension + + RCTNewArchEnabled + + UIAppFonts + + ExpensifyNeue-Regular.otf + ExpensifyNeue-Bold.otf + + + diff --git a/ios/LiveActivity/LiveActivity.entitlements b/ios/LiveActivity/LiveActivity.entitlements new file mode 100644 index 0000000000000..f52d3207d6e3e --- /dev/null +++ b/ios/LiveActivity/LiveActivity.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.expensify.new + + + diff --git a/ios/LiveActivityExtension.entitlements b/ios/LiveActivityExtension.entitlements new file mode 100644 index 0000000000000..f52d3207d6e3e --- /dev/null +++ b/ios/LiveActivityExtension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.expensify.new + + + diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index 36ca04fe34a9b..ce5643e9d7453 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -27,6 +27,16 @@ 47347EFF2DA5664A00633001 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47347EFE2DA5664A00633001 /* AppDelegate.swift */; }; 524F95D57E75496EBD14B0AA /* ExpensifyMono-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = A96F65C6624044318D21DAB1 /* ExpensifyMono-BoldItalic.otf */; }; 59164B2F48344A53975791A9 /* CustomEmojiNativeFont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B42BBCC5FB8E4E40B1A9202C /* CustomEmojiNativeFont.ttf */; }; + 6E13A7D92F519F8F00B2FA13 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E13A7D82F519F8F00B2FA13 /* WidgetKit.framework */; }; + 6E13A7DB2F519F8F00B2FA13 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E13A7DA2F519F8F00B2FA13 /* SwiftUI.framework */; }; + 6E13A7EA2F519F9000B2FA13 /* LiveActivityExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 6E13A7D72F519F8F00B2FA13 /* LiveActivityExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 6E13A80D2F51A2E700B2FA13 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6E13A8062F51A2E700B2FA13 /* Assets.xcassets */; }; + 6E13A80F2F51A2E700B2FA13 /* GpsTripAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E13A8072F51A2E700B2FA13 /* GpsTripAttributes.swift */; }; + 6E13A8102F51A2E700B2FA13 /* GpsTripLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E13A8082F51A2E700B2FA13 /* GpsTripLiveActivity.swift */; }; + 6E13A8112F51A2E700B2FA13 /* GpsTripWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E13A8092F51A2E700B2FA13 /* GpsTripWidgetBundle.swift */; }; + 6E13A8122F51A2E700B2FA13 /* GpsTripAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E13A8072F51A2E700B2FA13 /* GpsTripAttributes.swift */; }; + 6E13A8132F51A4DC00B2FA13 /* ExpensifyNeue-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 52796131E6554494B2DDB056 /* ExpensifyNeue-Bold.otf */; }; + 6E13A8142F51A4E800B2FA13 /* ExpensifyNeue-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = F4F8A052A22040339996324B /* ExpensifyNeue-Regular.otf */; }; 70CF6E82262E297300711ADC /* BootSplash.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 70CF6E81262E297300711ADC /* BootSplash.storyboard */; }; 7F9DD8DA2B2A445B005E3AFA /* ExpError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F9DD8D92B2A445B005E3AFA /* ExpError.swift */; }; 7FB680AE2CC94EDA006693CF /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7FB680AD2CC94EDA006693CF /* GoogleService-Info.plist */; }; @@ -52,6 +62,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 6E13A7E82F519F9000B2FA13 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 6E13A7D62F519F8F00B2FA13; + remoteInfo = LiveActivityExtension; + }; 7FD73CA02B23CE9500420AF3 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; @@ -75,6 +92,7 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( + 6E13A7EA2F519F9000B2FA13 /* LiveActivityExtension.appex in Embed Foundation Extensions */, 7FD73CA22B23CE9500420AF3 /* NotificationServiceExtension.appex in Embed Foundation Extensions */, E5A27BA12C7F427F002C36BF /* ShareViewController.appex in Embed Foundation Extensions */, ); @@ -115,6 +133,16 @@ 4D20D83B0E39BA6D21761E72 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-NewExpensify/ExpoModulesProvider.swift"; sourceTree = ""; }; 52796131E6554494B2DDB056 /* ExpensifyNeue-Bold.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNeue-Bold.otf"; path = "../assets/fonts/native/ExpensifyNeue-Bold.otf"; sourceTree = ""; }; 67D096CE2C3CB125AAC20F11 /* Pods-NotificationServiceExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationServiceExtension.debug.xcconfig"; path = "Target Support Files/Pods-NotificationServiceExtension/Pods-NotificationServiceExtension.debug.xcconfig"; sourceTree = ""; }; + 6E13A7D72F519F8F00B2FA13 /* LiveActivityExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = LiveActivityExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 6E13A7D82F519F8F00B2FA13 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; + 6E13A7DA2F519F8F00B2FA13 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; + 6E13A8052F51A21100B2FA13 /* LiveActivityExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LiveActivityExtension.entitlements; sourceTree = ""; }; + 6E13A8062F51A2E700B2FA13 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 6E13A8072F51A2E700B2FA13 /* GpsTripAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GpsTripAttributes.swift; sourceTree = ""; }; + 6E13A8082F51A2E700B2FA13 /* GpsTripLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GpsTripLiveActivity.swift; sourceTree = ""; }; + 6E13A8092F51A2E700B2FA13 /* GpsTripWidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GpsTripWidgetBundle.swift; sourceTree = ""; }; + 6E13A80A2F51A2E700B2FA13 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 6E13A80B2F51A2E700B2FA13 /* LiveActivity.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LiveActivity.entitlements; sourceTree = ""; }; 6E887A8466FD8FE3B8A0F396 /* Pods_NewExpensify.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NewExpensify.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6FFAB4E4A2A56FFA9B6357B3 /* Pods-NewExpensify.debugdevelopment.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify.debugdevelopment.xcconfig"; path = "Target Support Files/Pods-NewExpensify/Pods-NewExpensify.debugdevelopment.xcconfig"; sourceTree = ""; }; 70CF6E81262E297300711ADC /* BootSplash.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = BootSplash.storyboard; path = NewExpensify/BootSplash.storyboard; sourceTree = ""; }; @@ -174,6 +202,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 6E13A7D42F519F8F00B2FA13 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6E13A7DB2F519F8F00B2FA13 /* SwiftUI.framework in Frameworks */, + 6E13A7D92F519F8F00B2FA13 /* WidgetKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 7FD73C982B23CE9500420AF3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -224,6 +261,8 @@ ED2971642150620600B7C4FE /* JavaScriptCore.framework */, 6E887A8466FD8FE3B8A0F396 /* Pods_NewExpensify.framework */, 4521D653AC9D36713686E739 /* Pods_NotificationServiceExtension.framework */, + 6E13A7D82F519F8F00B2FA13 /* WidgetKit.framework */, + 6E13A7DA2F519F8F00B2FA13 /* SwiftUI.framework */, ); name = Frameworks; sourceTree = ""; @@ -244,6 +283,19 @@ name = ExpoModulesProviders; sourceTree = ""; }; + 6E13A80C2F51A2E700B2FA13 /* LiveActivity */ = { + isa = PBXGroup; + children = ( + 6E13A8062F51A2E700B2FA13 /* Assets.xcassets */, + 6E13A8072F51A2E700B2FA13 /* GpsTripAttributes.swift */, + 6E13A8082F51A2E700B2FA13 /* GpsTripLiveActivity.swift */, + 6E13A8092F51A2E700B2FA13 /* GpsTripWidgetBundle.swift */, + 6E13A80A2F51A2E700B2FA13 /* Info.plist */, + 6E13A80B2F51A2E700B2FA13 /* LiveActivity.entitlements */, + ); + path = LiveActivity; + sourceTree = ""; + }; 7FD73C9C2B23CE9500420AF3 /* NotificationServiceExtension */ = { isa = PBXGroup; children = ( @@ -265,6 +317,7 @@ 83CBB9F61A601CBA00E9B192 = { isa = PBXGroup; children = ( + 6E13A8052F51A21100B2FA13 /* LiveActivityExtension.entitlements */, 47347F002DA56FAC00633001 /* NewExpensify-Bridging-Header.h */, E5A27B8F2C7F2F51002C36BF /* RCTShareActionHandlerModule.h */, E5A27B902C7F2FA8002C36BF /* RCTShareActionHandlerModule.m */, @@ -281,6 +334,7 @@ 832341AE1AAA6A7D00B99B32 /* Libraries */, 7FD73C9C2B23CE9500420AF3 /* NotificationServiceExtension */, E5A27B982C7F427F002C36BF /* ShareViewController */, + 6E13A80C2F51A2E700B2FA13 /* LiveActivity */, 83CBBA001A601CBA00E9B192 /* Products */, 2D16E6871FA4F8E400B85C8A /* Frameworks */, EC29677F0A49C2946A495A33 /* Pods */, @@ -301,6 +355,7 @@ 13B07F961A680F5B00A75B9A /* New Expensify Dev.app */, 7FD73C9B2B23CE9500420AF3 /* NotificationServiceExtension.appex */, E5A27B972C7F427F002C36BF /* ShareViewController.appex */, + 6E13A7D72F519F8F00B2FA13 /* LiveActivityExtension.appex */, ); name = Products; sourceTree = ""; @@ -383,12 +438,30 @@ dependencies = ( 7FD73CA12B23CE9500420AF3 /* PBXTargetDependency */, E5A27BA02C7F427F002C36BF /* PBXTargetDependency */, + 6E13A7E92F519F9000B2FA13 /* PBXTargetDependency */, ); name = NewExpensify; productName = NewExpensify; productReference = 13B07F961A680F5B00A75B9A /* New Expensify Dev.app */; productType = "com.apple.product-type.application"; }; + 6E13A7D62F519F8F00B2FA13 /* LiveActivityExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6E13A7F32F519F9000B2FA13 /* Build configuration list for PBXNativeTarget "LiveActivityExtension" */; + buildPhases = ( + 6E13A7D32F519F8F00B2FA13 /* Sources */, + 6E13A7D42F519F8F00B2FA13 /* Frameworks */, + 6E13A7D52F519F8F00B2FA13 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = LiveActivityExtension; + productName = LiveActivityExtension; + productReference = 6E13A7D72F519F8F00B2FA13 /* LiveActivityExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; 7FD73C9A2B23CE9500420AF3 /* NotificationServiceExtension */ = { isa = PBXNativeTarget; buildConfigurationList = 7FD73CAA2B23CE9500420AF3 /* Build configuration list for PBXNativeTarget "NotificationServiceExtension" */; @@ -430,7 +503,7 @@ 83CBB9F71A601CBA00E9B192 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1540; + LastSwiftUpdateCheck = 2620; LastUpgradeCheck = 1130; TargetAttributes = { 13B07F861A680F5B00A75B9A = { @@ -438,6 +511,9 @@ LastSwiftMigration = 1230; ProvisioningStyle = Manual; }; + 6E13A7D62F519F8F00B2FA13 = { + CreatedOnToolsVersion = 26.2; + }; 7FD73C9A2B23CE9500420AF3 = { CreatedOnToolsVersion = 15.0.1; DevelopmentTeam = 368M544MTT; @@ -464,6 +540,7 @@ 13B07F861A680F5B00A75B9A /* NewExpensify */, 7FD73C9A2B23CE9500420AF3 /* NotificationServiceExtension */, E5A27B962C7F427F002C36BF /* ShareViewController */, + 6E13A7D62F519F8F00B2FA13 /* LiveActivityExtension */, ); }; /* End PBXProject section */ @@ -499,6 +576,16 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 6E13A7D52F519F8F00B2FA13 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6E13A8142F51A4E800B2FA13 /* ExpensifyNeue-Regular.otf in Resources */, + 6E13A8132F51A4DC00B2FA13 /* ExpensifyNeue-Bold.otf in Resources */, + 6E13A80D2F51A2E700B2FA13 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 7FD73C992B23CE9500420AF3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -772,10 +859,21 @@ 0F5E5350263B73FD004CA14F /* EnvironmentChecker.m in Sources */, 374FB8D728A133FE000D84EF /* OriginImageRequestHandler.mm in Sources */, DD79042B2792E76D004484B4 /* RCTBootSplash.mm in Sources */, + 6E13A8122F51A2E700B2FA13 /* GpsTripAttributes.swift in Sources */, DDCB2E57F334C143AC462B43 /* ExpoModulesProvider.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; + 6E13A7D32F519F8F00B2FA13 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6E13A80F2F51A2E700B2FA13 /* GpsTripAttributes.swift in Sources */, + 6E13A8102F51A2E700B2FA13 /* GpsTripLiveActivity.swift in Sources */, + 6E13A8112F51A2E700B2FA13 /* GpsTripWidgetBundle.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 7FD73C972B23CE9500420AF3 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -796,6 +894,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 6E13A7E92F519F9000B2FA13 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 6E13A7D62F519F8F00B2FA13 /* LiveActivityExtension */; + targetProxy = 6E13A7E82F519F9000B2FA13 /* PBXContainerItemProxy */; + }; 7FD73CA12B23CE9500420AF3 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 7FD73C9A2B23CE9500420AF3 /* NotificationServiceExtension */; @@ -1123,6 +1226,465 @@ }; name = ReleaseDevelopment; }; + 6E13A7EB2F519F9000B2FA13 /* DebugDevelopment */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = LiveActivityExtension.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ""; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = LiveActivity/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = LiveActivity; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; + PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev.LiveActivity; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugDevelopment; + }; + 6E13A7EC2F519F9000B2FA13 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = LiveActivityExtension.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ""; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = LiveActivity/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = LiveActivity; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; + PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev.LiveActivity; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 6E13A7ED2F519F9000B2FA13 /* DebugAdHoc */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = LiveActivityExtension.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ""; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = LiveActivity/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = LiveActivity; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; + PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev.LiveActivity; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugAdHoc; + }; + 6E13A7EE2F519F9000B2FA13 /* DebugProduction */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = LiveActivityExtension.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ""; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = LiveActivity/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = LiveActivity; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; + PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev.LiveActivity; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugProduction; + }; + 6E13A7EF2F519F9000B2FA13 /* ReleaseDevelopment */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = LiveActivityExtension.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = ""; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = LiveActivity/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = LiveActivity; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; + PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev.LiveActivity; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = ReleaseDevelopment; + }; + 6E13A7F02F519F9000B2FA13 /* ReleaseAdHoc */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = LiveActivityExtension.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = ""; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = LiveActivity/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = LiveActivity; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; + PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev.LiveActivity; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = ReleaseAdHoc; + }; + 6E13A7F12F519F9000B2FA13 /* ReleaseProduction */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = LiveActivityExtension.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = ""; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = LiveActivity/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = LiveActivity; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 16.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; + PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev.LiveActivity; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = ReleaseProduction; + }; 7FD73CA42B23CE9500420AF3 /* DebugDevelopment */ = { isa = XCBuildConfiguration; baseConfigurationReference = 14430647C56C5AD89A19FCF5 /* Pods-NotificationServiceExtension.debugdevelopment.xcconfig */; @@ -2774,6 +3336,20 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = DebugDevelopment; }; + 6E13A7F32F519F9000B2FA13 /* Build configuration list for PBXNativeTarget "LiveActivityExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6E13A7EB2F519F9000B2FA13 /* DebugDevelopment */, + 6E13A7EC2F519F9000B2FA13 /* Debug */, + 6E13A7ED2F519F9000B2FA13 /* DebugAdHoc */, + 6E13A7EE2F519F9000B2FA13 /* DebugProduction */, + 6E13A7EF2F519F9000B2FA13 /* ReleaseDevelopment */, + 6E13A7F02F519F9000B2FA13 /* ReleaseAdHoc */, + 6E13A7F12F519F9000B2FA13 /* ReleaseProduction */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugDevelopment; + }; 7FD73CAA2B23CE9500420AF3 /* Build configuration list for PBXNativeTarget "NotificationServiceExtension" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index cef0e7dff94f6..8ff5ba799238a 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -132,6 +132,8 @@ UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown + NSSupportsLiveActivities + UIViewControllerBasedStatusBarAppearance diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 59ef8f14fe9f0..fc75cd794c409 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -481,6 +481,7 @@ PODS: - React-jsinspectorcdp - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimeexecutor - React-runtimescheduler - React-utils @@ -506,6 +507,7 @@ PODS: - React-jsinspectorcdp - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimeexecutor - React-runtimescheduler - React-utils @@ -530,6 +532,7 @@ PODS: - React-jsinspectorcdp - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimeexecutor - React-runtimescheduler - React-utils @@ -556,6 +559,7 @@ PODS: - React-jsinspectorcdp - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimeexecutor - React-runtimescheduler - React-utils @@ -581,6 +585,7 @@ PODS: - React-jsinspectorcdp - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimeexecutor - React-runtimescheduler - React-utils @@ -606,6 +611,7 @@ PODS: - React-jsinspectorcdp - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimeexecutor - React-runtimescheduler - React-utils @@ -631,6 +637,7 @@ PODS: - React-jsinspectorcdp - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimeexecutor - React-runtimescheduler - React-utils @@ -656,6 +663,7 @@ PODS: - React-jsinspectorcdp - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimeexecutor - React-runtimescheduler - React-utils @@ -681,6 +689,7 @@ PODS: - React-jsinspectorcdp - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimeexecutor - React-runtimescheduler - React-utils @@ -706,6 +715,7 @@ PODS: - React-jsinspectorcdp - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimeexecutor - React-runtimescheduler - React-utils @@ -731,6 +741,7 @@ PODS: - React-jsinspectorcdp - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimeexecutor - React-runtimescheduler - React-utils @@ -756,6 +767,7 @@ PODS: - React-jsinspectorcdp - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimeexecutor - React-runtimescheduler - React-utils @@ -781,6 +793,7 @@ PODS: - React-jsinspectorcdp - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimeexecutor - React-runtimescheduler - React-utils @@ -806,6 +819,7 @@ PODS: - React-jsinspectorcdp - React-jsitooling - React-perflogger + - React-rendererconsistency - React-runtimeexecutor - React-runtimescheduler - React-utils @@ -4495,7 +4509,7 @@ SPEC CHECKSUMS: AirshipServiceExtension: 50d11b2f62c4a490d4e81a1c36f70e2ecb70a27e AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73 AppLogs: 3bc4e9b141dbf265b9464409caaa40416a9ee0e0 - boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 + boost: 659a89341ea4ab3df8259733813b52f26d8be9a5 DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb EXConstants: fd688cef4e401dcf798a021cfb5d87c890c30ba3 EXImageLoader: 4d3d3284141f1a45006cc4d0844061c182daf7ee @@ -4559,7 +4573,7 @@ SPEC CHECKSUMS: React: 2073376f47c71b7e9a0af7535986a77522ce1049 React-callinvoker: 751b6f2c83347a0486391c3f266f291f0f53b27e React-Codegen: 4b8b4817cea7a54b83851d4c1f91f79aa73de30a - React-Core: dff5d29973349b11dd6631c9498456d75f846d5e + React-Core: aeebd9b37ac383279f610f1e53f66b9931686a41 React-CoreModules: c0ae04452e4c5d30e06f8e94692a49107657f537 React-cxxreact: 376fd672c95dfb64ad5cc246e6a1e9edb78dec4c React-debug: d4955c86870792887ed695df6ebf0e94e39dc7e1 @@ -4609,7 +4623,7 @@ SPEC CHECKSUMS: react-native-webview: cdce419e8022d0ef6f07db21890631258e7a9e6e React-NativeModulesApple: 8c7eb6057b00c191a11ad5ced41826ec5a0e4d78 React-oscompat: 93b5535ea7f7dff46aaee4f78309a70979bdde9d - React-perflogger: 5536d2df3d18fe0920263466f7b46a56351c0510 + React-perflogger: e7dcbfcb796d346be7936b75740c3e27a4bb3977 React-performancetimeline: c6c9393c1a0453a51e1852e3531defe60790b36c React-RCTActionSheet: 42195ae666e6d79b4af2346770f765b7c29435b9 React-RCTAnimation: fa103ccc3503b1ed8dedca7e62e7823937748843 @@ -4624,7 +4638,7 @@ SPEC CHECKSUMS: React-RCTSettings: 71f5c7fd7b5f4e725a4e2114a4b4373d0e46048f React-RCTText: b94d4699b49285bee22b8ebf768924d607eccee3 React-RCTVibration: 6e3993c4f6c36a3899059f9a9ead560ddaf5a7d7 - React-rendererconsistency: 612d0f6603d9837bb1236d7fd5194203b35c8799 + React-rendererconsistency: bef28690433e2b4bb00c2f884b22b86e61a430f2 React-renderercss: e5c2c3b84976f7a587cde8423c671db07a6a77da React-rendererdebug: cc7a6131733605b8897754f72c0c35c79f77da9e React-RuntimeApple: 3f96102fc1ebf738d36719cdce5422a5769293fb diff --git a/src/components/GPSTripStateChecker/index.native.tsx b/src/components/GPSTripStateChecker/index.native.tsx index 14bed0a4549cd..c95697ea6be00 100644 --- a/src/components/GPSTripStateChecker/index.native.tsx +++ b/src/components/GPSTripStateChecker/index.native.tsx @@ -8,7 +8,8 @@ import useOnyx from '@hooks/useOnyx'; import {stopGpsTrip} from '@libs/GPSDraftDetailsUtils'; import Navigation from '@libs/Navigation/Navigation'; import {generateReportID} from '@libs/ReportUtils'; -import {BACKGROUND_LOCATION_TRACKING_TASK_NAME, getBackgroundLocationTaskOptions} from '@pages/iou/request/step/IOURequestStepDistanceGPS/const'; +import {BACKGROUND_LOCATION_TASK_OPTIONS, BACKGROUND_LOCATION_TRACKING_TASK_NAME} from '@pages/iou/request/step/IOURequestStepDistanceGPS/const'; +import {checkAndCleanGpsNotification, startGpsTripNotification} from '@pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -23,6 +24,8 @@ function GPSTripStateChecker() { const {splashScreenState} = useSplashScreenState(); + const reportID = gpsDraftDetails?.reportID ?? generateReportID(); + useUpdateGpsTripOnReconnect(); useEffect(() => { @@ -37,6 +40,7 @@ function GPSTripStateChecker() { } handleGpsTripInProgressOnAppRestart(); + checkAndCleanGpsNotification(); return () => { hasStartedLocationUpdatesAsync(BACKGROUND_LOCATION_TRACKING_TASK_NAME).then((isRunning) => { @@ -50,7 +54,6 @@ function GPSTripStateChecker() { }, []); const navigateToGpsScreen = () => { - const reportID = gpsDraftDetails?.reportID ?? generateReportID(); Navigation.navigate(ROUTES.DISTANCE_REQUEST_CREATE_TAB_GPS.getRoute(CONST.IOU.ACTION.CREATE, CONST.IOU.TYPE.CREATE, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, reportID)); }; @@ -58,15 +61,24 @@ function GPSTripStateChecker() { const isBackgroundTaskRunning = await hasStartedLocationUpdatesAsync(BACKGROUND_LOCATION_TRACKING_TASK_NAME); if (isBackgroundTaskRunning) { + if (gpsDraftDetails?.unit) { + startGpsTripNotification(translate, reportID, gpsDraftDetails?.unit, gpsDraftDetails?.distanceInMeters); + } return; } - const notificationTitle = translate('gps.notification.title'); - const notificationBody = translate('gps.notification.body'); + try { + await startLocationUpdatesAsync(BACKGROUND_LOCATION_TRACKING_TASK_NAME, BACKGROUND_LOCATION_TASK_OPTIONS); + } catch (error) { + console.error('[GPS distance request] Failed to restart location tracking', error); + return; + } + + if (!gpsDraftDetails?.unit) { + return; + } - await startLocationUpdatesAsync(BACKGROUND_LOCATION_TRACKING_TASK_NAME, getBackgroundLocationTaskOptions(notificationTitle, notificationBody)).catch((error) => - console.error('[GPS distance request] Failed to restart location tracking', error), - ); + startGpsTripNotification(translate, reportID, gpsDraftDetails?.unit, gpsDraftDetails?.distanceInMeters); }; const onContinueTrip = () => { diff --git a/src/languages/de.ts b/src/languages/de.ts index a700401564181..d134bcde930b2 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -7633,6 +7633,10 @@ Fordern Sie Spesendetails wie Belege und Beschreibungen an, legen Sie Limits und title: 'GPS-Tracking läuft', body: 'Gehe zur App, um abzuschließen', }, + liveActivity: { + subtitle: 'Tracking distance', + button: 'View progress', + }, continueGpsTripModal: { title: 'GPS-Fahrtaufzeichnung fortsetzen?', prompt: 'Es sieht so aus, als ob die App während deiner letzten GPS-Fahrt geschlossen wurde. Möchtest du die Aufzeichnung dieser Fahrt fortsetzen?', diff --git a/src/languages/en.ts b/src/languages/en.ts index 932830247c899..5fa87aac8d245 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -7591,6 +7591,10 @@ const translations = { title: 'GPS tracking in progress', body: 'Go to the app to finish', }, + liveActivity: { + subtitle: 'Tracking distance', + button: 'View progress', + }, continueGpsTripModal: { title: 'Continue GPS trip recording?', prompt: 'Looks like the app closed during your last GPS trip. Would you like to continue recording from that trip?', diff --git a/src/languages/es.ts b/src/languages/es.ts index 5981ec7df6d4b..6e1813e52f77e 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -8778,6 +8778,10 @@ ${amount} para ${merchant} - ${date}`, title: 'Seguimiento GPS en curso', body: 'Ve a la app para finalizar', }, + liveActivity: { + subtitle: 'Tracking distance', + button: 'View progress', + }, continueGpsTripModal: { title: '¿Continuar el registro del viaje por GPS?', prompt: 'Parece que la app se cerró durante tu último viaje por GPS. ¿Te gustaría continuar grabando ese viaje?', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 86b0d26bd8585..48b883ea33f1a 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -7656,6 +7656,10 @@ Rendez obligatoires des informations de dépense comme les reçus et les descrip title: 'Suivi GPS en cours', body: 'Allez dans l’application pour terminer', }, + liveActivity: { + subtitle: 'Tracking distance', + button: 'View progress', + }, continueGpsTripModal: { title: 'Continuer l’enregistrement GPS du trajet ?', prompt: 'On dirait que l’application s’est fermée pendant votre dernier trajet GPS. Souhaitez-vous continuer l’enregistrement de ce trajet ?', diff --git a/src/languages/it.ts b/src/languages/it.ts index ff0e09d0b5430..b6fbacf9cbf39 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -7620,6 +7620,10 @@ Richiedi dettagli sulle spese come ricevute e descrizioni, imposta limiti e valo title: 'Monitoraggio GPS in corso', body: "Vai all'app per completare", }, + liveActivity: { + subtitle: 'Tracking distance', + button: 'View progress', + }, continueGpsTripModal: { title: 'Continuare la registrazione del viaggio GPS?', prompt: 'Sembra che l’app si sia chiusa durante il tuo ultimo viaggio GPS. Vuoi continuare la registrazione da quel viaggio?', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index a6ee0c6e58983..94ed1072c691e 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -7534,6 +7534,10 @@ ${reportName} title: 'GPS追跡を実行中', body: 'アプリに移動して完了する', }, + liveActivity: { + subtitle: 'Tracking distance', + button: 'View progress', + }, continueGpsTripModal: { title: 'GPSでの走行記録を続けますか?', prompt: '前回のGPS走行中にアプリが終了したようです。その走行の記録を続けますか?', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index fd796ef923856..e6a0d56a0dbf6 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -7599,6 +7599,10 @@ Vereis onkostendetails zoals bonnen en beschrijvingen, stel limieten en standaar title: 'GPS-tracking bezig', body: 'Ga naar de app om te voltooien', }, + liveActivity: { + subtitle: 'Tracking distance', + button: 'View progress', + }, continueGpsTripModal: { title: 'GPS-ritregistratie voortzetten?', prompt: 'Het lijkt erop dat de app is afgesloten tijdens je laatste GPS-rit. Wil je het opnemen van die rit hervatten?', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index f32a109aa38f0..fc7f0a289ac87 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -7586,6 +7586,10 @@ Wymagaj szczegółów wydatków, takich jak paragony i opisy, ustawiaj limity i title: 'Trwa śledzenie GPS', body: 'Przejdź do aplikacji, aby dokończyć', }, + liveActivity: { + subtitle: 'Tracking distance', + button: 'View progress', + }, continueGpsTripModal: { title: 'Kontynuować rejestrowanie trasy GPS?', prompt: 'Wygląda na to, że aplikacja została zamknięta podczas Twojej ostatniej trasy GPS. Czy chcesz kontynuować nagrywanie z tamtej trasy?', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index a4410051c3df4..bfd3acc3ea328 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -7591,6 +7591,10 @@ Exija dados de despesas como recibos e descrições, defina limites e padrões e title: 'Rastreamento por GPS em andamento', body: 'Vá para o app para concluir', }, + liveActivity: { + subtitle: 'Tracking distance', + button: 'View progress', + }, continueGpsTripModal: { title: 'Continuar gravação de viagem por GPS?', prompt: 'Parece que o app foi fechado durante sua última viagem com GPS. Gostaria de continuar a gravação daquela viagem?', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index daea37217dde0..8ecdae1c5a8df 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -7419,6 +7419,10 @@ ${reportName} title: '正在进行 GPS 追踪', body: '前往应用完成操作', }, + liveActivity: { + subtitle: 'Tracking distance', + button: 'View progress', + }, continueGpsTripModal: { title: '继续记录 GPS 行程?', prompt: '看起来上次的 GPS 行程中应用已关闭。要从那次行程继续记录吗?', diff --git a/src/libs/GPSDraftDetailsUtils.ts b/src/libs/GPSDraftDetailsUtils.ts index ff82b47624704..612b7c2d558f9 100644 --- a/src/libs/GPSDraftDetailsUtils.ts +++ b/src/libs/GPSDraftDetailsUtils.ts @@ -1,6 +1,7 @@ import {hasStartedLocationUpdatesAsync, reverseGeocodeAsync, stopLocationUpdatesAsync} from 'expo-location'; import OnyxUtils from 'react-native-onyx/dist/OnyxUtils'; import {BACKGROUND_LOCATION_TRACKING_TASK_NAME} from '@pages/iou/request/step/IOURequestStepDistanceGPS/const'; +import {stopGpsTripNotification} from '@pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications'; import ONYXKEYS from '@src/ONYXKEYS'; import type {GpsDraftDetails} from '@src/types/onyx'; import type {Unit} from '@src/types/onyx/Policy'; @@ -100,6 +101,8 @@ async function stopGpsTrip(isOffline: boolean) { setIsTracking(false); + stopGpsTripNotification(); + const gpsTrip = await OnyxUtils.get(ONYXKEYS.GPS_DRAFT_DETAILS); const lastPoint = gpsTrip?.gpsPoints?.at(-1); diff --git a/src/libs/Navigation/linkingConfig/index.ts b/src/libs/Navigation/linkingConfig/index.ts index 4eec394d5bcbb..15a1b22502914 100644 --- a/src/libs/Navigation/linkingConfig/index.ts +++ b/src/libs/Navigation/linkingConfig/index.ts @@ -4,11 +4,13 @@ import getAdaptedStateFromPath from '@libs/Navigation/helpers/getAdaptedStateFro import type {RootNavigatorParamList} from '@libs/Navigation/types'; import {config} from './config'; import prefixes from './prefixes'; +import subscribe from './subscribe'; const linkingConfig: LinkingOptions = { getStateFromPath: getAdaptedStateFromPath, prefixes, config, + subscribe, }; // eslint-disable-next-line import/prefer-default-export diff --git a/src/libs/Navigation/linkingConfig/subscribe.ts b/src/libs/Navigation/linkingConfig/subscribe.ts new file mode 100644 index 0000000000000..a3c503423c885 --- /dev/null +++ b/src/libs/Navigation/linkingConfig/subscribe.ts @@ -0,0 +1,27 @@ +import type {LinkingOptions} from '@react-navigation/native'; +import {findFocusedRoute} from '@react-navigation/native'; +import {Linking} from 'react-native'; +import navigationRef from '@libs/Navigation/navigationRef'; +import type {RootNavigatorParamList} from '@libs/Navigation/types'; +import ROUTES from '@src/ROUTES'; + +const subscribe: LinkingOptions['subscribe'] = (listener) => { + const subscription = Linking.addEventListener('url', ({url}: {url: string}) => { + // Skip GPS distance screen deep links when the user is already on that screen. + // This prevents a re-navigation with animation when tapping the GPS live update notification + // while the GPS screen is already visible. + if (url.includes(ROUTES.DISTANCE_REQUEST_CREATE_TAB_GPS.route)) { + const state = navigationRef.current?.getRootState(); + if (state) { + const currentRoute = findFocusedRoute(state); + if (currentRoute?.name === ROUTES.DISTANCE_REQUEST_CREATE_TAB_GPS.route) { + return; + } + } + } + listener(url); + }); + return () => subscription.remove(); +}; + +export default subscribe; diff --git a/src/libs/actions/GPSDraftDetails.ts b/src/libs/actions/GPSDraftDetails.ts index 4e6a28ec7bdf8..f8307f67bcedb 100644 --- a/src/libs/actions/GPSDraftDetails.ts +++ b/src/libs/actions/GPSDraftDetails.ts @@ -1,8 +1,10 @@ import Onyx from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import {GPS_DISTANCE_INTERVAL_METERS} from '@pages/iou/request/step/IOURequestStepDistanceGPS/const'; +import {updateGpsTripNotification} from '@pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications'; import ONYXKEYS from '@src/ONYXKEYS'; import type {GpsDraftDetails} from '@src/types/onyx'; +import type {Unit} from '@src/types/onyx/Policy'; import geodesicDistance from '@src/utils/geodesicDistance'; function resetGPSDraftDetails() { @@ -21,7 +23,7 @@ function setEndAddress(endAddress: GpsDraftDetails['endAddress']) { }); } -function initGpsDraft(reportID: string) { +function initGpsDraft(reportID: string, unit: Unit) { Onyx.merge(ONYXKEYS.GPS_DRAFT_DETAILS, { gpsPoints: [], isTracking: true, @@ -29,6 +31,7 @@ function initGpsDraft(reportID: string) { startAddress: {value: '', type: 'coordinates'}, endAddress: {value: '', type: 'coordinates'}, reportID, + unit, }); } @@ -69,6 +72,10 @@ function addGpsPoints(gpsDraftDetails: OnyxEntry, newGpsPoints: const updatedGpsPoints = [...capturedPoints, ...gpsPointsToAdd]; + if (updatedDistance > 0) { + updateGpsTripNotification(updatedDistance); + } + Onyx.merge(ONYXKEYS.GPS_DRAFT_DETAILS, { gpsPoints: updatedGpsPoints, distanceInMeters: updatedDistance, diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/DistanceCounter/index.tsx b/src/pages/iou/request/step/IOURequestStepDistanceGPS/DistanceCounter/index.tsx index a6dbe954ae27e..cac36808a7c92 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/DistanceCounter/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/DistanceCounter/index.tsx @@ -1,38 +1,22 @@ import React from 'react'; import {View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; import Text from '@components/Text'; -import useDefaultExpensePolicy from '@hooks/useDefaultExpensePolicy'; import useOnyx from '@hooks/useOnyx'; -import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; -import shouldUseDefaultExpensePolicyUtil from '@libs/shouldUseDefaultExpensePolicy'; -import type {IOUType} from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Report, Transaction} from '@src/types/onyx'; +import type {Unit} from '@src/types/onyx/Policy'; import TripStatusIndicator from './TripStatusIndicator'; type DistanceCounterProps = { - /** The transaction object being modified in Onyx */ - transaction: OnyxEntry; - /** The report corresponding to the reportID in the route params */ - report: OnyxEntry; - /** The type of IOU report, i.e. split, request, send, track */ - iouType: IOUType; + /** Distance unit of the ongoing GPS trip */ + unit: Unit; }; -function DistanceCounter({report, transaction, iouType}: DistanceCounterProps) { +function DistanceCounter({unit}: DistanceCounterProps) { const styles = useThemeStyles(); - const policy = usePolicy(report?.policyID); const [gpsDraftDetails] = useOnyx(ONYXKEYS.GPS_DRAFT_DETAILS); - const defaultExpensePolicy = useDefaultExpensePolicy(); - - const shouldUseDefaultExpensePolicy = shouldUseDefaultExpensePolicyUtil(iouType, defaultExpensePolicy); - - const unit = DistanceRequestUtils.getRate({transaction, policy: shouldUseDefaultExpensePolicy ? defaultExpensePolicy : policy}).unit; - const distance = DistanceRequestUtils.convertDistanceUnit(gpsDraftDetails?.distanceInMeters ?? 0, unit).toFixed(1); const tripInProgressOrStopped = (gpsDraftDetails?.gpsPoints?.length ?? 0) > 0 || gpsDraftDetails?.isTracking; diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSButtons/index.tsx b/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSButtons/index.tsx index 9a6a153d72814..881a971ea7fe0 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSButtons/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSButtons/index.tsx @@ -12,19 +12,31 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {initGpsDraft, resetGPSDraftDetails} from '@libs/actions/GPSDraftDetails'; import {stopGpsTrip} from '@libs/GPSDraftDetailsUtils'; import BackgroundLocationPermissionsFlow from '@pages/iou/request/step/IOURequestStepDistanceGPS/BackgroundLocationPermissionsFlow'; -import {BACKGROUND_LOCATION_TRACKING_TASK_NAME, getBackgroundLocationTaskOptions} from '@pages/iou/request/step/IOURequestStepDistanceGPS/const'; +import {BACKGROUND_LOCATION_TASK_OPTIONS, BACKGROUND_LOCATION_TRACKING_TASK_NAME} from '@pages/iou/request/step/IOURequestStepDistanceGPS/const'; +import {startGpsTripNotification} from '@pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {Unit} from '@src/types/onyx/Policy'; import openSettings from './openSettings'; type ButtonsProps = { + /** Function to call when user clicks next button after ending a trip */ navigateToNextStep: () => void; + + /** Function to call when there is an error starting GPS tracking */ setShouldShowStartError: React.Dispatch>; + + /** Function to call when there is a permissions error */ setShouldShowPermissionsError: React.Dispatch>; + + /** reportID of the ongoing GPS trip */ reportID: string; + + /** Distance unit of the ongoing GPS trip */ + unit: Unit; }; -function GPSButtons({navigateToNextStep, setShouldShowStartError, setShouldShowPermissionsError, reportID}: ButtonsProps) { +function GPSButtons({navigateToNextStep, setShouldShowStartError, setShouldShowPermissionsError, reportID, unit}: ButtonsProps) { const [startPermissionsFlow, setStartPermissionsFlow] = useState(false); const [showLocationRequiredModal, setShowLocationRequiredModal] = useState(false); const [showDiscardConfirmation, setShowDiscardConfirmation] = useState(false); @@ -55,17 +67,14 @@ function GPSButtons({navigateToNextStep, setShouldShowStartError, setShouldShowP const startGpsTrip = async () => { try { - await startLocationUpdatesAsync( - BACKGROUND_LOCATION_TRACKING_TASK_NAME, - getBackgroundLocationTaskOptions(translate('gps.notification.title'), translate('gps.notification.body')), - ); + await startLocationUpdatesAsync(BACKGROUND_LOCATION_TRACKING_TASK_NAME, BACKGROUND_LOCATION_TASK_OPTIONS); } catch (error) { console.error('[GPS distance request] Failed to start location tracking', error); setShouldShowStartError(true); return; } - - initGpsDraft(reportID); + initGpsDraft(reportID, unit); + startGpsTripNotification(translate, reportID, unit); }; const onNext = () => { diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications/index.ios.ts b/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications/index.ios.ts new file mode 100644 index 0000000000000..49fa8cba95433 --- /dev/null +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications/index.ios.ts @@ -0,0 +1,96 @@ +import Airship from '@ua/react-native-airship'; +import type {LocalizedTranslate} from '@components/LocaleContextProvider'; +import DistanceRequestUtils from '@libs/DistanceRequestUtils'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; +import type {Unit} from '@src/types/onyx/Policy'; + +const ATTRIBUTES_TYPE = 'GpsTripAttributes'; + +let activityId: string | null = null; +let distanceUnit: Unit | null = null; + +function startGpsTripNotification(translate: LocalizedTranslate, reportID: string, unit: Unit, distanceInMeters = 0) { + const subtitle = translate('gps.liveActivity.subtitle'); + const buttonText = translate('gps.liveActivity.button'); + const distanceUnitLong = unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.miles') : translate('common.kilometers'); + + const deepLink = ROUTES.DISTANCE_REQUEST_CREATE_TAB_GPS.getRoute(CONST.IOU.ACTION.CREATE, CONST.IOU.TYPE.CREATE, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, reportID); + + let distance = 0; + if (distanceInMeters !== undefined && distanceInMeters !== 0) { + distance = DistanceRequestUtils.convertDistanceUnit(distanceInMeters, unit); + } + + Airship.iOS.liveActivityManager + .start({ + attributesType: ATTRIBUTES_TYPE, + content: { + state: {distance}, + relevanceScore: 100, + }, + attributes: {deepLink, subtitle, distanceUnit: unit, distanceUnitLong, buttonText}, + }) + .then((activity) => { + activityId = activity.id; + distanceUnit = unit; + }) + .catch((error: unknown) => console.error('[GPS Live Activity] Failed to start', error)); +} + +function updateGpsTripNotification(distanceInMeters: number) { + if (activityId === null || distanceUnit === null) { + console.error('[GPS Live Activity] Failed to start update: activityId or distanceUnit is null'); + return; + } + + const distance = DistanceRequestUtils.convertDistanceUnit(distanceInMeters, distanceUnit); + + Airship.iOS.liveActivityManager + .update({ + attributesType: ATTRIBUTES_TYPE, + activityId, + content: { + state: {distance}, + relevanceScore: 100, + }, + }) + .catch((error: unknown) => console.error('[GPS Live Activity] Failed to update', error)); +} + +function stopGpsTripNotification() { + if (activityId === null) { + return; + } + + Airship.iOS.liveActivityManager + .end({ + attributesType: ATTRIBUTES_TYPE, + activityId, + dismissalPolicy: {type: 'immediate'}, + }) + .catch((error: unknown) => console.error('[GPS Live Activity] Failed to end', error)); + + activityId = null; + distanceUnit = null; +} + +async function checkAndCleanGpsNotification() { + const liveActivities = await Airship.iOS.liveActivityManager.listAll(); + + for (const liveActivity of liveActivities) { + if (liveActivity.attributeTypes !== ATTRIBUTES_TYPE) { + continue; + } + + Airship.iOS.liveActivityManager + .end({ + attributesType: ATTRIBUTES_TYPE, + activityId: liveActivity.id, + dismissalPolicy: {type: 'immediate'}, + }) + .catch((error: unknown) => console.error('[GPS Live Activity] Failed to end', error)); + } +} + +export {startGpsTripNotification, updateGpsTripNotification, stopGpsTripNotification, checkAndCleanGpsNotification}; diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications/index.ts b/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications/index.ts new file mode 100644 index 0000000000000..e5fcf55f0656c --- /dev/null +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications/index.ts @@ -0,0 +1,17 @@ +// Android and iOS only, no-op for other platforms +import type {LocalizedTranslate} from '@components/LocaleContextProvider'; +import type {Unit} from '@src/types/onyx/Policy'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function startGpsTripNotification(_translate: LocalizedTranslate, _reportID: string, _unit: Unit, _distanceInMeters?: number) {} + +function stopGpsTripNotification() {} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function updateGpsTripNotification(_distanceInMeters: number) {} + +async function checkAndCleanGpsNotification(): Promise { + // no-op +} + +export {startGpsTripNotification, stopGpsTripNotification, updateGpsTripNotification, checkAndCleanGpsNotification}; diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/const.ts b/src/pages/iou/request/step/IOURequestStepDistanceGPS/const.ts index 4982cbc608ff7..bf99b81fd270f 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/const.ts +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/const.ts @@ -3,19 +3,11 @@ import type {LocationTaskOptions} from 'expo-location'; const GPS_DISTANCE_INTERVAL_METERS = 100; -function getBackgroundLocationTaskOptions(notificationTitle: string, notificationBody: string): LocationTaskOptions { - return { - accuracy: Accuracy.Highest, - distanceInterval: GPS_DISTANCE_INTERVAL_METERS, - showsBackgroundLocationIndicator: true, - foregroundService: { - notificationTitle, - notificationBody, - killServiceOnDestroy: true, - }, - }; -} +const BACKGROUND_LOCATION_TASK_OPTIONS: LocationTaskOptions = { + accuracy: Accuracy.Highest, + distanceInterval: GPS_DISTANCE_INTERVAL_METERS, +}; const BACKGROUND_LOCATION_TRACKING_TASK_NAME = 'background-location-tracking'; -export {getBackgroundLocationTaskOptions, BACKGROUND_LOCATION_TRACKING_TASK_NAME, GPS_DISTANCE_INTERVAL_METERS}; +export {BACKGROUND_LOCATION_TASK_OPTIONS, BACKGROUND_LOCATION_TRACKING_TASK_NAME, GPS_DISTANCE_INTERVAL_METERS}; diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx index ba4bb8307ac93..6059008b1a703 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx @@ -147,11 +147,7 @@ function IOURequestStepDistanceGPS({ shouldShowNotFoundPage={shouldShowNotFoundPage} shouldShowWrapper={!isCreatingNewRequest} > - + @@ -165,6 +161,7 @@ function IOURequestStepDistanceGPS({ setShouldShowStartError={setShouldShowStartError} setShouldShowPermissionsError={setShouldShowPermissionsError} reportID={reportID} + unit={unit} /> diff --git a/src/pages/settings/InitialSettingsPage.tsx b/src/pages/settings/InitialSettingsPage.tsx index 51370a2ca6de1..592cdc0363e67 100755 --- a/src/pages/settings/InitialSettingsPage.tsx +++ b/src/pages/settings/InitialSettingsPage.tsx @@ -47,6 +47,7 @@ import {getProfilePageBrickRoadIndicator} from '@libs/UserUtils'; import type SETTINGS_TO_RHP from '@navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP'; import {showContextMenu} from '@pages/inbox/report/ContextMenu/ReportActionContextMenu'; import {BACKGROUND_LOCATION_TRACKING_TASK_NAME} from '@pages/iou/request/step/IOURequestStepDistanceGPS/const'; +import {stopGpsTripNotification} from '@pages/iou/request/step/IOURequestStepDistanceGPS/GPSNotifications'; import variables from '@styles/variables'; import {confirmReadyToOpenApp} from '@userActions/App'; import {openExternalLink, openOldDotLink} from '@userActions/Link'; @@ -214,6 +215,7 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr return; } if (isTrackingGPS) { + stopGpsTripNotification(); stopLocationUpdatesAsync(BACKGROUND_LOCATION_TRACKING_TASK_NAME).catch((error) => console.error('[GPS distance request] Failed to stop location tracking', error)); } signOut(true); diff --git a/src/types/onyx/GpsDraftDetails.ts b/src/types/onyx/GpsDraftDetails.ts index a2a99c3d8eb13..b4cff16905b3c 100644 --- a/src/types/onyx/GpsDraftDetails.ts +++ b/src/types/onyx/GpsDraftDetails.ts @@ -1,3 +1,5 @@ +import type {Unit} from './Policy'; + /** * Stores data from GPS trip (GPS distance request) */ @@ -34,6 +36,9 @@ type GpsDraftDetails = { /** reportID of the ongoing GPS trip */ reportID: string; + + /** Distance unit of the ongoing GPS trip */ + unit: Unit; }; export default GpsDraftDetails;