diff --git a/Kickstarter-iOS/AppDelegateViewModel.swift b/Kickstarter-iOS/AppDelegateViewModel.swift index 018f4073fc..6ae8b428d3 100644 --- a/Kickstarter-iOS/AppDelegateViewModel.swift +++ b/Kickstarter-iOS/AppDelegateViewModel.swift @@ -1,4 +1,5 @@ import AppboyKit +import AppTrackingTransparency import KsApi import Library import Prelude @@ -180,6 +181,9 @@ public protocol AppDelegateViewModelOutputs { /// Emits when we should register the device push token in Segment Analytics. var registerPushTokenInSegment: Signal { get } + /// Emits when application didFinishLaunchingWithOptions. + var requestATTrackingAuthorizationStatus: Signal { get } + /// Emits when our config updates with the enabled state for Semgent Analytics. var segmentIsEnabled: Signal { get } @@ -785,6 +789,14 @@ public final class AppDelegateViewModel: AppDelegateViewModelType, AppDelegateVi self.brazeWillDisplayInAppMessageReturnProperty <~ self.brazeWillDisplayInAppMessageProperty.signal .skipNil() .map { _ in .displayInAppMessageNow } + + self.requestATTrackingAuthorizationStatus = self.applicationLaunchOptionsProperty.signal + .skipNil() + .ksr_delay(.seconds(1), on: AppEnvironment.current.scheduler) + .map { _ -> ATTrackingAuthorizationStatus in + guard featureConsentManagementDialogEnabled() else { return .notDetermined } + return atTrackingAuthorizationStatus() + } } public var inputs: AppDelegateViewModelInputs { return self } @@ -956,6 +968,7 @@ public final class AppDelegateViewModel: AppDelegateViewModelType, AppDelegateVi public let pushTokenRegistrationStarted: Signal<(), Never> public let pushTokenSuccessfullyRegistered: Signal public let registerPushTokenInSegment: Signal + public let requestATTrackingAuthorizationStatus: Signal public let segmentIsEnabled: Signal public let setApplicationShortcutItems: Signal<[ShortcutItem], Never> public let showAlert: Signal @@ -965,6 +978,27 @@ public final class AppDelegateViewModel: AppDelegateViewModelType, AppDelegateVi public let updateConfigInEnvironment: Signal } +private func atTrackingAuthorizationStatus() -> ATTrackingAuthorizationStatus { + var authorizationStatus: ATTrackingAuthorizationStatus = .notDetermined + + ATTrackingManager.requestTrackingAuthorization(completionHandler: { status in + switch status { + case .notDetermined: + authorizationStatus = .notDetermined + case .authorized: + authorizationStatus = .authorized + case .denied: + authorizationStatus = .denied + case .restricted: + authorizationStatus = .restricted + @unknown default: + authorizationStatus = .notDetermined + } + }) + + return authorizationStatus +} + /// Handles the deeplink route with both an id and text based name for a deeplink to categories. private func deepLinkCategories(rawParams: [String: String]) -> (Param?, Param?) { let parentCategoryParams = rawParams["parent_category_id"] diff --git a/Kickstarter-iOS/AppDelegateViewModelTests.swift b/Kickstarter-iOS/AppDelegateViewModelTests.swift index e766e0244e..e7e4d7cf65 100644 --- a/Kickstarter-iOS/AppDelegateViewModelTests.swift +++ b/Kickstarter-iOS/AppDelegateViewModelTests.swift @@ -42,6 +42,7 @@ final class AppDelegateViewModelTests: TestCase { private let pushRegistrationStarted = TestObserver<(), Never>() private let pushTokenSuccessfullyRegistered = TestObserver() private let registerPushTokenInSegment = TestObserver() + private let requestATTrackingAuthorizationStatus = TestObserver() private let setApplicationShortcutItems = TestObserver<[ShortcutItem], Never>() private let segmentIsEnabled = TestObserver() private let showAlert = TestObserver() @@ -96,6 +97,8 @@ final class AppDelegateViewModelTests: TestCase { self.vm.outputs.pushTokenRegistrationStarted.observe(self.pushRegistrationStarted.observer) self.vm.outputs.pushTokenSuccessfullyRegistered.observe(self.pushTokenSuccessfullyRegistered.observer) self.vm.outputs.registerPushTokenInSegment.observe(self.registerPushTokenInSegment.observer) + self.vm.outputs.requestATTrackingAuthorizationStatus + .observe(self.requestATTrackingAuthorizationStatus.observer) self.vm.outputs.setApplicationShortcutItems.observe(self.setApplicationShortcutItems.observer) self.vm.outputs.showAlert.observe(self.showAlert.observer) self.vm.outputs.segmentIsEnabled.observe(self.segmentIsEnabled.observer) @@ -3004,6 +3007,16 @@ final class AppDelegateViewModelTests: TestCase { self.updateCurrentUserInEnvironment.assertValues([user, updatedUser]) } } + + func testRequestATTrackingAuthorizationStatus_CalledOnceOnDidFinishLaunching() { + self.requestATTrackingAuthorizationStatus.assertValueCount(0) + + self.vm.inputs.applicationDidFinishLaunching(application: UIApplication.shared, launchOptions: nil) + + self.scheduler.advance(by: .seconds(1)) + + self.requestATTrackingAuthorizationStatus.assertValueCount(1) + } } private let backingForCreatorPushData: [String: Any] = [ diff --git a/Kickstarter-iOS/Info.plist b/Kickstarter-iOS/Info.plist index cc301f760b..3b2828b51e 100644 --- a/Kickstarter-iOS/Info.plist +++ b/Kickstarter-iOS/Info.plist @@ -2,6 +2,8 @@ + NSUserTrackingUsageDescription + We use personal data to provide a good experience on Kickstarter, and to help connect you with projects you'll love. CFBundleDevelopmentRegion en CFBundleExecutable diff --git a/Kickstarter.xcodeproj/project.pbxproj b/Kickstarter.xcodeproj/project.pbxproj index 487b450642..a40567a1a6 100644 --- a/Kickstarter.xcodeproj/project.pbxproj +++ b/Kickstarter.xcodeproj/project.pbxproj @@ -485,6 +485,7 @@ 6067BCE9293E49AC0036ABB1 /* FacebookResetPasswordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6067BCE7293E48140036ABB1 /* FacebookResetPasswordViewController.swift */; }; 6067BCEC293E49F00036ABB1 /* FacebookResetPasswordViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6067BCEA293E49CB0036ABB1 /* FacebookResetPasswordViewModel.swift */; }; 6067BCF2293FC3520036ABB1 /* FacebookResetPasswordViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6067BCEF293FC10E0036ABB1 /* FacebookResetPasswordViewModelTests.swift */; }; + 606F214429799A1200BA5CDF /* ATTrackingAuthorizationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 606F214229799A0000BA5CDF /* ATTrackingAuthorizationStatus.swift */; }; 608E7A5328ABDBAE00289E92 /* SetYourPasswordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 608E7A5128ABD5E700289E92 /* SetYourPasswordViewController.swift */; }; 608E7A5628ABE6CD00289E92 /* SetYourPasswordViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 608E7A5428ABE27400289E92 /* SetYourPasswordViewModel.swift */; }; 60DA50EB28B689A4002E2DF1 /* SetYourPasswordViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60DA50E928B68990002E2DF1 /* SetYourPasswordViewModelTests.swift */; }; @@ -2069,6 +2070,7 @@ 6067BCE7293E48140036ABB1 /* FacebookResetPasswordViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FacebookResetPasswordViewController.swift; sourceTree = ""; }; 6067BCEA293E49CB0036ABB1 /* FacebookResetPasswordViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FacebookResetPasswordViewModel.swift; sourceTree = ""; }; 6067BCEF293FC10E0036ABB1 /* FacebookResetPasswordViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FacebookResetPasswordViewModelTests.swift; sourceTree = ""; }; + 606F214229799A0000BA5CDF /* ATTrackingAuthorizationStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTrackingAuthorizationStatus.swift; sourceTree = ""; }; 608E7A5128ABD5E700289E92 /* SetYourPasswordViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetYourPasswordViewController.swift; sourceTree = ""; }; 608E7A5428ABE27400289E92 /* SetYourPasswordViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetYourPasswordViewModel.swift; sourceTree = ""; }; 60DA50E928B68990002E2DF1 /* SetYourPasswordViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetYourPasswordViewModelTests.swift; sourceTree = ""; }; @@ -5971,6 +5973,7 @@ 8A213CE4239EAEA400BBB4C7 /* TrackingClientType.swift */, 94BE15C125E857C4007CD9A4 /* TrackingHelpers.swift */, 94BE15C925E96F06007CD9A4 /* TrackingHelpersTests.swift */, + 606F214229799A0000BA5CDF /* ATTrackingAuthorizationStatus.swift */, ); path = Tracking; sourceTree = ""; @@ -7733,6 +7736,7 @@ 8A6C58932475E5950098D5A2 /* UIRefreshControl+StartRefreshing.swift in Sources */, D7A37CCF1E2FF93D00EA066D /* SearchEmptyStateCellViewModel.swift in Sources */, 80D73AF61D50F1A60099231F /* Navigation.swift in Sources */, + 606F214429799A1200BA5CDF /* ATTrackingAuthorizationStatus.swift in Sources */, 473DE012273C502F0033331D /* ProjectRisksCellViewModel.swift in Sources */, A755115F1C8642C3005355CF /* Format.swift in Sources */, 598D96CB1D42AE85003F3F66 /* ActivitySampleProjectCellViewModel.swift in Sources */, diff --git a/Library/Tracking/ATTrackingAuthorizationStatus.swift b/Library/Tracking/ATTrackingAuthorizationStatus.swift new file mode 100644 index 0000000000..bb0258a42b --- /dev/null +++ b/Library/Tracking/ATTrackingAuthorizationStatus.swift @@ -0,0 +1,6 @@ +public enum ATTrackingAuthorizationStatus { + case authorized + case denied + case notDetermined + case restricted +}