diff --git a/Kickstarter-iOS/AppDelegate.swift b/Kickstarter-iOS/AppDelegate.swift index fa29fd33d3..38a5eefe3d 100644 --- a/Kickstarter-iOS/AppDelegate.swift +++ b/Kickstarter-iOS/AppDelegate.swift @@ -183,11 +183,15 @@ internal final class AppDelegate: UIResponder, UIApplicationDelegate { #if RELEASE || APPCENTER self.viewModel.outputs.configureFirebase .observeForUI() - .observeValues { + .observeValues { [weak self] in + guard let strongSelf = self else { return } + FirebaseApp.configure() AppEnvironment.current.ksrAnalytics.logEventCallback = { event, _ in Crashlytics.crashlytics().log(format: "%@", arguments: getVaList([event])) } + + strongSelf.configureRemoteConfig() } #endif @@ -254,9 +258,10 @@ internal final class AppDelegate: UIResponder, UIApplicationDelegate { .observeValues { [weak self] featureFlagClient in guard let strongSelf = self else { return } + // TODO: Will remove this method and input/output with the full removal of Optimizely code AppEnvironment.updateOptimizelyClient(featureFlagClient) - strongSelf.viewModel.inputs.didUpdateOptimizelyClient(featureFlagClient) + strongSelf.viewModel.inputs.didUpdateRemoteConfigClient() } self.viewModel.outputs.segmentIsEnabled @@ -422,6 +427,46 @@ internal final class AppDelegate: UIResponder, UIApplicationDelegate { let task = session.dataTask(with: url) task.resume() } + + private func configureRemoteConfig() { + let remoteConfigClient = RemoteConfigClient(with: RemoteConfig.remoteConfig()) + + AppEnvironment.updateRemoteConfigClient(remoteConfigClient) + + let appDefaults: [String: Any?] = [ + RemoteConfigFeature.consentManagementDialogEnabled.rawValue: false, + RemoteConfigFeature.facebookLoginInterstitialEnabled.rawValue: false + ] + + AppEnvironment.current.remoteConfigClient?.setDefaults(appDefaults as? [String: NSObject]) + + AppEnvironment.current.remoteConfigClient?.activate { _, error in + guard let remoteConfigActivationError = error else { + print("🔮 Remote Config SDK Successfully Activated") + + self.viewModel.inputs.didUpdateRemoteConfigClient() + return + } + + print("🔴 Remote Config SDK Activation Failed with Error: \(remoteConfigActivationError.localizedDescription)") + + Crashlytics.crashlytics().record(error: remoteConfigActivationError) + + self.viewModel.inputs.remoteConfigClientConfigurationFailed() + } + + AppEnvironment.current.remoteConfigClient?.fetch { _, _ in } + + _ = AppEnvironment.current.remoteConfigClient?.addOnConfigUpdateListener { _, error in + guard let realtimeUpdateError = error else { + print("🔮 Remote Config Key/Value Pair Updated") + + return + } + + print("🔴 Remote Config SDK Config Update Listener Failure: \(realtimeUpdateError.localizedDescription)") + } + } } // MARK: - URLSessionTaskDelegate diff --git a/Kickstarter-iOS/AppDelegateViewModel.swift b/Kickstarter-iOS/AppDelegateViewModel.swift index 9eb002c7ad..4496fdec2b 100644 --- a/Kickstarter-iOS/AppDelegateViewModel.swift +++ b/Kickstarter-iOS/AppDelegateViewModel.swift @@ -67,14 +67,14 @@ public protocol AppDelegateViewModelInputs { /// Call when the config has been updated the AppEnvironment func didUpdateConfig(_ config: Config) - /// Call when the Optimizely client has been updated in the AppEnvironment - func didUpdateOptimizelyClient(_ client: OptimizelyClientType) + /// Call when the Remote Config client has been updated in the AppEnvironment + func didUpdateRemoteConfigClient() /// Call when the redirect URL has been found, see `findRedirectUrl` for more information. func foundRedirectUrl(_ url: URL) - /// Call when Optimizely configuration has failed - func optimizelyClientConfigurationFailed() + /// Call when Remote Config configuration has failed + func remoteConfigClientConfigurationFailed() /// Call when Perimeter X Captcha is triggered func perimeterXCaptchaTriggeredWithUserInfo(_ userInfo: [AnyHashable: Any]?) @@ -265,12 +265,12 @@ public final class AppDelegateViewModel: AppDelegateViewModelType, AppDelegateVi .skipNil() .mapConst(Notification(name: .ksr_configUpdated, object: nil)) - let optimizelyClientConfiguredNotification = self.didUpdateOptimizelyClientProperty.signal - .mapConst(Notification(name: .ksr_optimizelyClientConfigured, object: nil)) + let remoteConfigClientConfiguredNotification = self.didUpdateRemoteConfigClientProperty.signal + .mapConst(Notification(name: .ksr_remoteConfigClientConfigured, object: nil)) - let optimizelyClientConfigurationFailedNotification = self.optimizelyClientConfigurationFailedProperty + let remoteConfigClientConfigurationFailedNotification = self.remoteConfigClientConfigurationFailedProperty .signal - .mapConst(Notification(name: .ksr_optimizelyClientConfigurationFailed, object: nil)) + .mapConst(Notification(name: .ksr_remoteConfigClientConfigurationFailed, object: nil)) let appEnteredBackgroundNotification = self.applicationDidEnterBackgroundProperty.signal .mapConst(Notification(name: .ksr_applicationDidEnterBackground, object: nil)) @@ -278,8 +278,8 @@ public final class AppDelegateViewModel: AppDelegateViewModelType, AppDelegateVi self.postNotification = Signal.merge( currentUserUpdatedNotification, configUpdatedNotification, - optimizelyClientConfiguredNotification, - optimizelyClientConfigurationFailedNotification, + remoteConfigClientConfiguredNotification, + remoteConfigClientConfigurationFailedNotification, appEnteredBackgroundNotification ) @@ -887,9 +887,9 @@ public final class AppDelegateViewModel: AppDelegateViewModelType, AppDelegateVi self.didUpdateConfigProperty.value = config } - fileprivate let didUpdateOptimizelyClientProperty = MutableProperty(nil) - public func didUpdateOptimizelyClient(_ client: OptimizelyClientType) { - self.didUpdateOptimizelyClientProperty.value = client + fileprivate let didUpdateRemoteConfigClientProperty = MutableProperty(()) + public func didUpdateRemoteConfigClient() { + self.didUpdateRemoteConfigClientProperty.value = () } private let foundRedirectUrlProperty = MutableProperty(nil) @@ -938,9 +938,9 @@ public final class AppDelegateViewModel: AppDelegateViewModelType, AppDelegateVi } // FIXME: Currently not used with `MockOptimizelyClient`, but could be when we implement the next real feature flagging client. - fileprivate let optimizelyClientConfigurationFailedProperty = MutableProperty(()) - public func optimizelyClientConfigurationFailed() { - self.optimizelyClientConfigurationFailedProperty.value = () + fileprivate let remoteConfigClientConfigurationFailedProperty = MutableProperty(()) + public func remoteConfigClientConfigurationFailed() { + self.remoteConfigClientConfigurationFailedProperty.value = () } private let perimeterXCaptchaTriggeredWithUserInfoProperty = MutableProperty<[AnyHashable: Any]?>(nil) diff --git a/Kickstarter-iOS/AppDelegateViewModelTests.swift b/Kickstarter-iOS/AppDelegateViewModelTests.swift index 32d1843842..d8354e307c 100644 --- a/Kickstarter-iOS/AppDelegateViewModelTests.swift +++ b/Kickstarter-iOS/AppDelegateViewModelTests.swift @@ -318,9 +318,9 @@ final class AppDelegateViewModelTests: TestCase { self.configureFeatureFlagClient.assertValueCount(1) - self.vm.inputs.didUpdateOptimizelyClient(MockOptimizelyClient()) + self.vm.inputs.didUpdateRemoteConfigClient() - self.postNotificationName.assertValues([.ksr_optimizelyClientConfigured]) + self.postNotificationName.assertValues([.ksr_remoteConfigClientConfigured]) } } diff --git a/Kickstarter-iOS/Features/Discovery/Controller/DiscoveryViewController.swift b/Kickstarter-iOS/Features/Discovery/Controller/DiscoveryViewController.swift index 6e9a5cb771..84dbb99003 100644 --- a/Kickstarter-iOS/Features/Discovery/Controller/DiscoveryViewController.swift +++ b/Kickstarter-iOS/Features/Discovery/Controller/DiscoveryViewController.swift @@ -9,8 +9,8 @@ internal final class DiscoveryViewController: UIViewController { private var recommendationsChangedObserver: Any? private weak var navigationHeaderViewController: DiscoveryNavigationHeaderViewController! - private var optimizelyConfiguredObserver: Any? - private var optimizelyConfigurationFailedObserver: Any? + private var remoteConfigConfiguredObserver: Any? + private var remoteConfigConfigurationFailedObserver: Any? private weak var pageViewController: UIPageViewController! private weak var sortPagerViewController: SortPagerViewController! @@ -48,18 +48,18 @@ internal final class DiscoveryViewController: UIViewController { self?.viewModel.inputs.didChangeRecommendationsSetting() } - self.optimizelyConfiguredObserver = NotificationCenter.default - .addObserver(forName: .ksr_optimizelyClientConfigured, object: nil, queue: nil) { [weak self] _ in - self?.viewModel.inputs.optimizelyClientConfigured() + self.remoteConfigConfiguredObserver = NotificationCenter.default + .addObserver(forName: .ksr_remoteConfigClientConfigured, object: nil, queue: nil) { [weak self] _ in + self?.viewModel.inputs.remoteConfigClientConfigured() } - self.optimizelyConfigurationFailedObserver = NotificationCenter.default + self.remoteConfigConfigurationFailedObserver = NotificationCenter.default .addObserver( - forName: .ksr_optimizelyClientConfigurationFailed, + forName: .ksr_remoteConfigClientConfigurationFailed, object: nil, queue: nil ) { [weak self] _ in - self?.viewModel.inputs.optimizelyClientConfigurationFailed() + self?.viewModel.inputs.remoteConfigClientConfigurationFailed() } self.viewModel.inputs.viewDidLoad() @@ -67,8 +67,8 @@ internal final class DiscoveryViewController: UIViewController { deinit { [ - self.optimizelyConfiguredObserver, - self.optimizelyConfigurationFailedObserver, + self.remoteConfigConfiguredObserver, + self.remoteConfigConfigurationFailedObserver, self.recommendationsChangedObserver ].forEach { $0.doIfSome(NotificationCenter.default.removeObserver) } } diff --git a/Kickstarter-iOS/Features/LoginTout/Controller/LoginToutViewController.swift b/Kickstarter-iOS/Features/LoginTout/Controller/LoginToutViewController.swift index fa3a063b05..30313388f3 100644 --- a/Kickstarter-iOS/Features/LoginTout/Controller/LoginToutViewController.swift +++ b/Kickstarter-iOS/Features/LoginTout/Controller/LoginToutViewController.swift @@ -199,7 +199,7 @@ public final class LoginToutViewController: UIViewController, MFMailComposeViewC AppEnvironment.login(accessTokenEnv) - if featureFacebookLoginDeprecationEnabled(), accessTokenEnv.user.needsPassword == true { + if featureFacebookLoginInterstitialEnabled(), accessTokenEnv.user.needsPassword == true { strongSelf.pushSetYourPasswordViewController() return } @@ -235,7 +235,7 @@ public final class LoginToutViewController: UIViewController, MFMailComposeViewC .observeValues { [weak self] error in guard let strongSelf = self else { return } - if featureFacebookLoginDeprecationEnabled() { + if featureFacebookLoginInterstitialEnabled() { strongSelf.present( UIAlertController.facebookDeprecationNewPasswordOptionAlert( loginHandler: { [weak self] _ in diff --git a/Kickstarter.xcodeproj/project.pbxproj b/Kickstarter.xcodeproj/project.pbxproj index 2965b3e8c5..aaf43f368f 100644 --- a/Kickstarter.xcodeproj/project.pbxproj +++ b/Kickstarter.xcodeproj/project.pbxproj @@ -233,14 +233,7 @@ 19047FCB2889CDAC00BDD1A8 /* GraphAPI.CreateSetupIntentInput+CreateSetupIntentInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19047FCA2889CDAC00BDD1A8 /* GraphAPI.CreateSetupIntentInput+CreateSetupIntentInput.swift */; }; 19047FCF2889D4BC00BDD1A8 /* GraphAPI.CreateSetupIntentInput+CreateSetupIntentInputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19047FCE2889D4BC00BDD1A8 /* GraphAPI.CreateSetupIntentInput+CreateSetupIntentInputTests.swift */; }; 19047FD12889D6DC00BDD1A8 /* ClientSecretEnvelope+CreateSetupIntentMutation.DataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19047FD02889D6DC00BDD1A8 /* ClientSecretEnvelope+CreateSetupIntentMutation.DataTests.swift */; }; - 1905787B28F8CD2500428375 /* ReactiveSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 1905787A28F8CD2500428375 /* ReactiveSwift */; }; - 1905788128F8CD7000428375 /* ReactiveSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 1905788028F8CD7000428375 /* ReactiveSwift */; }; - 191A4B4028FF386C009D62A5 /* ReactiveSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 191A4B3F28FF386C009D62A5 /* ReactiveSwift */; }; - 191A4B4228FF3897009D62A5 /* ReactiveExtensions in Frameworks */ = {isa = PBXBuildFile; productRef = 191A4B4128FF3897009D62A5 /* ReactiveExtensions */; }; - 191A4B4428FF395E009D62A5 /* ReactiveExtensions in Frameworks */ = {isa = PBXBuildFile; productRef = 191A4B4328FF395E009D62A5 /* ReactiveExtensions */; }; - 191A4B4628FF3965009D62A5 /* ReactiveSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 191A4B4528FF3965009D62A5 /* ReactiveSwift */; }; 191A4B4C28FF3AF8009D62A5 /* ReactiveExtensions-TestHelpers in Frameworks */ = {isa = PBXBuildFile; productRef = 191A4B4B28FF3AF8009D62A5 /* ReactiveExtensions-TestHelpers */; }; - 191A4B5028FF44D8009D62A5 /* ReactiveExtensions in Frameworks */ = {isa = PBXBuildFile; productRef = 191A4B4F28FF44D8009D62A5 /* ReactiveExtensions */; }; 191EDC6728E29BB9009B41B2 /* PerimeterX in Frameworks */ = {isa = PBXBuildFile; productRef = 191EDC6628E29BB9009B41B2 /* PerimeterX */; }; 1923770A28DA2AE300F68635 /* Stripe+PaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1923770928DA2AE300F68635 /* Stripe+PaymentMethod.swift */; }; 1937A71F28C94FFC00DD732D /* SettingsFormFieldView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7790DF882200D3BD005DBB11 /* SettingsFormFieldView.xib */; }; @@ -264,17 +257,16 @@ 19821C87299D3E8300EF312F /* AppboySegment in Frameworks */ = {isa = PBXBuildFile; productRef = 19821C86299D3E8300EF312F /* AppboySegment */; }; 198E574B28E2705100D5B8A9 /* PerimeterX in Frameworks */ = {isa = PBXBuildFile; productRef = 198E574A28E2705100D5B8A9 /* PerimeterX */; }; 198E574D28E2705E00D5B8A9 /* PerimeterX in Frameworks */ = {isa = PBXBuildFile; productRef = 198E574C28E2705E00D5B8A9 /* PerimeterX */; }; - 1998BCA828F60E8900D04077 /* ReactiveExtensions in Frameworks */ = {isa = PBXBuildFile; productRef = 1998BCA728F60E8900D04077 /* ReactiveExtensions */; }; 1998BCB028F60EC300D04077 /* ReactiveExtensions-TestHelpers in Frameworks */ = {isa = PBXBuildFile; productRef = 1998BCAF28F60EC300D04077 /* ReactiveExtensions-TestHelpers */; }; 1998BCB228F60ED400D04077 /* ReactiveExtensions in Frameworks */ = {isa = PBXBuildFile; productRef = 1998BCB128F60ED400D04077 /* ReactiveExtensions */; }; + 19A6665F2A045F2A00708F8B /* FirebaseRemoteConfig in Frameworks */ = {isa = PBXBuildFile; productRef = 19A6665E2A045F2A00708F8B /* FirebaseRemoteConfig */; }; + 19A666612A0472A500708F8B /* RemoteConfligClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A666602A0472A500708F8B /* RemoteConfligClient.swift */; }; + 19A666632A0488F500708F8B /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A755113C1C8642B3005355CF /* Library.framework */; }; 19A97CE228C7DA7B0031B857 /* ActivitiesDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = A75AB1F81C8A84B5002FC3E6 /* ActivitiesDataSource.swift */; }; 19A97CF228C7E2D30031B857 /* CategoryPillCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A97CF128C7E2D30031B857 /* CategoryPillCell.swift */; }; 19A97D1928C7F0E30031B857 /* DiscoveryPageViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7ED202E1E8323E900BFFA01 /* DiscoveryPageViewControllerTests.swift */; }; 19A97D1A28C7F0EC0031B857 /* DiscoveryNavigationHeaderViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7ED20301E8323E900BFFA01 /* DiscoveryNavigationHeaderViewControllerTests.swift */; }; 19A97D2328C7FCF00031B857 /* ManagePledgeViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61440FF23200FD3002A6507 /* ManagePledgeViewControllerTests.swift */; }; - 19BF226128D10497007F4197 /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = 19BF226028D10497007F4197 /* FirebaseAnalytics */; }; - 19BF226328D10497007F4197 /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = 19BF226228D10497007F4197 /* FirebaseCrashlytics */; }; - 19BF226528D10497007F4197 /* FirebasePerformance in Frameworks */ = {isa = PBXBuildFile; productRef = 19BF226428D10497007F4197 /* FirebasePerformance */; }; 19C8E56929A9249D007C3504 /* TextInputFieldModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19C8E56829A9249D007C3504 /* TextInputFieldModifiers.swift */; }; 19D988482979CA4E00A5EE61 /* BrazePushEnvelope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19D988472979CA4E00A5EE61 /* BrazePushEnvelope.swift */; }; 19E9F01729C0F9A20002AD69 /* MockAppTrackingTransparency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6008633E29BF750700B87B39 /* MockAppTrackingTransparency.swift */; }; @@ -492,12 +484,20 @@ 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 */; }; + 606C45F129FACD49001BA067 /* RemoteConfigClientType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 606C45F029FACD49001BA067 /* RemoteConfigClientType.swift */; }; + 606C45F329FACD68001BA067 /* RemoteConfigFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 606C45F229FACD68001BA067 /* RemoteConfigFeature.swift */; }; + 606C45F529FACD78001BA067 /* RemoteConfigFeature+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 606C45F429FACD78001BA067 /* RemoteConfigFeature+Helpers.swift */; }; + 606C45F829FACD9F001BA067 /* RemoteConfigFeature+HelpersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 606C45F629FACD94001BA067 /* RemoteConfigFeature+HelpersTests.swift */; }; + 606C45FA29FACE17001BA067 /* MockRemoteConfigClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 606C45F929FACE17001BA067 /* MockRemoteConfigClient.swift */; }; 606F214429799A1200BA5CDF /* ATTrackingAuthorizationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 606F214229799A0000BA5CDF /* ATTrackingAuthorizationStatus.swift */; }; 606F215C299D3FA900BA5CDF /* GraphAPI.TriggerCapiEventInput+TriggerCapiEventInputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 606F215A299D3EF800BA5CDF /* GraphAPI.TriggerCapiEventInput+TriggerCapiEventInputTests.swift */; }; 606F215E299D414800BA5CDF /* GraphAPI.TriggerCapiEventInput+TriggerCapiEventInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 606F215D299D414800BA5CDF /* GraphAPI.TriggerCapiEventInput+TriggerCapiEventInput.swift */; }; 606F2166299D456D00BA5CDF /* TriggerCapiEvent.graphql in Sources */ = {isa = PBXBuildFile; fileRef = 606F2165299D456D00BA5CDF /* TriggerCapiEvent.graphql */; }; 606F2168299D45F900BA5CDF /* TriggerCapiEventInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 606F2167299D45F900BA5CDF /* TriggerCapiEventInput.swift */; }; 606F216B299E8D8500BA5CDF /* TriggerCapiEventInputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 606F2169299E8C9F00BA5CDF /* TriggerCapiEventInputTests.swift */; }; + 6078106B2A0419130050D4F7 /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = 6078106A2A0419130050D4F7 /* FirebaseCrashlytics */; }; + 6078106D2A0419170050D4F7 /* FirebasePerformance in Frameworks */ = {isa = PBXBuildFile; productRef = 6078106C2A0419170050D4F7 /* FirebasePerformance */; }; + 6078106F2A04191C0050D4F7 /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = 6078106E2A04191C0050D4F7 /* FirebaseAnalytics */; }; 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 */; }; @@ -750,7 +750,6 @@ 8A8C6136243FBA640092B682 /* PledgePaymentMethodsDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A8C6135243FBA640092B682 /* PledgePaymentMethodsDataSourceTests.swift */; }; 8AA3DB30250AB4AB009AC8EA /* UIAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0156B4181D10B419000C4252 /* UIAlertController.swift */; }; 8AA3DB31250AB4E5009AC8EA /* UIStackView+BackgroundColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6508F342049C45D002DCC01 /* UIStackView+BackgroundColor.swift */; }; - 8AA3DB32250AC42F009AC8EA /* Library.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A755113C1C8642B3005355CF /* Library.framework */; }; 8AA3DB33250AE40C009AC8EA /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04AAC17218BB70B00CF713E /* SettingsViewModel.swift */; }; 8AA3DB34250AE410009AC8EA /* SettingsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04AAC09218BB70800CF713E /* SettingsViewModelTests.swift */; }; 8AA3DB35250AE46D009AC8EA /* SettingsAccountViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60C8BEA21430BC200D96152 /* SettingsAccountViewModel.swift */; }; @@ -1167,7 +1166,6 @@ D002CAE3218CF91D009783F2 /* WatchProjectInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = D002CAE2218CF91D009783F2 /* WatchProjectInput.swift */; }; D002CAE5218CF951009783F2 /* WatchProjectResponseEnvelope.swift in Sources */ = {isa = PBXBuildFile; fileRef = D002CAE4218CF951009783F2 /* WatchProjectResponseEnvelope.swift */; }; D00A376E225BDAF800F46F47 /* UIAlertControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69BACEF21C856F2006EAA00 /* UIAlertControllerTests.swift */; }; - D01587591EEB2DE4006E7684 /* KsApi.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D01587501EEB2DE4006E7684 /* KsApi.framework */; }; D015882B1EEB2ED7006E7684 /* NSURLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015876A1EEB2ED6006E7684 /* NSURLSession.swift */; }; D015882D1EEB2ED7006E7684 /* BasicHTTPAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015876D1EEB2ED6006E7684 /* BasicHTTPAuth.swift */; }; D015882F1EEB2ED7006E7684 /* ClientAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015876E1EEB2ED6006E7684 /* ClientAuth.swift */; }; @@ -1846,6 +1844,7 @@ 194154D228D928C9004648C8 /* CreatePaymentSourceSetupIntentInputTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePaymentSourceSetupIntentInputTests.swift; sourceTree = ""; }; 194154D428D92A26004648C8 /* CreatePaymentSourceSetupIntentClientSecret+ConstructorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CreatePaymentSourceSetupIntentClientSecret+ConstructorTests.swift"; sourceTree = ""; }; 194520C4288859A600CA9B88 /* PaymentCardTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentCardTextField.swift; sourceTree = ""; }; + 19A666602A0472A500708F8B /* RemoteConfligClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteConfligClient.swift; sourceTree = ""; }; 19A97CF128C7E2D30031B857 /* CategoryPillCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryPillCell.swift; sourceTree = ""; }; 19C8E56829A9249D007C3504 /* TextInputFieldModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextInputFieldModifiers.swift; sourceTree = ""; }; 19D988472979CA4E00A5EE61 /* BrazePushEnvelope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrazePushEnvelope.swift; sourceTree = ""; }; @@ -2076,6 +2075,11 @@ 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 = ""; }; + 606C45F029FACD49001BA067 /* RemoteConfigClientType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteConfigClientType.swift; sourceTree = ""; }; + 606C45F229FACD68001BA067 /* RemoteConfigFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteConfigFeature.swift; sourceTree = ""; }; + 606C45F429FACD78001BA067 /* RemoteConfigFeature+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RemoteConfigFeature+Helpers.swift"; sourceTree = ""; }; + 606C45F629FACD94001BA067 /* RemoteConfigFeature+HelpersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RemoteConfigFeature+HelpersTests.swift"; sourceTree = ""; }; + 606C45F929FACE17001BA067 /* MockRemoteConfigClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRemoteConfigClient.swift; sourceTree = ""; }; 606F214229799A0000BA5CDF /* ATTrackingAuthorizationStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTrackingAuthorizationStatus.swift; sourceTree = ""; }; 606F215A299D3EF800BA5CDF /* GraphAPI.TriggerCapiEventInput+TriggerCapiEventInputTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GraphAPI.TriggerCapiEventInput+TriggerCapiEventInputTests.swift"; sourceTree = ""; }; 606F215D299D414800BA5CDF /* GraphAPI.TriggerCapiEventInput+TriggerCapiEventInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GraphAPI.TriggerCapiEventInput+TriggerCapiEventInput.swift"; sourceTree = ""; }; @@ -3177,15 +3181,17 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D0D10BB81EEB394D005EBAD0 /* KsApi.framework in Frameworks */, + 6078106D2A0419170050D4F7 /* FirebasePerformance in Frameworks */, 60DA50FE28C38DDB002E2DF1 /* AlamofireImage in Frameworks */, 06634FC72807A4EB00950F60 /* Prelude_UIKit in Frameworks */, - 191A4B4028FF386C009D62A5 /* ReactiveSwift in Frameworks */, - D0D10BB81EEB394D005EBAD0 /* KsApi.framework in Frameworks */, 198E574B28E2705100D5B8A9 /* PerimeterX in Frameworks */, 1981AC90289075D900BB4897 /* Stripe in Frameworks */, + 6078106F2A04191C0050D4F7 /* FirebaseAnalytics in Frameworks */, 19821C87299D3E8300EF312F /* AppboySegment in Frameworks */, 60DA510F28C7E04B002E2DF1 /* Kingfisher in Frameworks */, - 191A4B4228FF3897009D62A5 /* ReactiveExtensions in Frameworks */, + 6078106B2A0419130050D4F7 /* FirebaseCrashlytics in Frameworks */, + 19A6665F2A045F2A00708F8B /* FirebaseRemoteConfig in Frameworks */, 606754BF28CF91DD0033CD5E /* FacebookLogin in Frameworks */, 606754BD28CF91D60033CD5E /* FacebookCore in Frameworks */, ); @@ -3195,9 +3201,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 19A666632A0488F500708F8B /* Library.framework in Frameworks */, 19F91B14289C8097000AEC6A /* Stripe in Frameworks */, - 191A4B5028FF44D8009D62A5 /* ReactiveExtensions in Frameworks */, - 8AA3DB32250AC42F009AC8EA /* Library.framework in Frameworks */, 191A4B4C28FF3AF8009D62A5 /* ReactiveExtensions-TestHelpers in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3207,8 +3212,6 @@ buildActionMask = 2147483647; files = ( A76127C01C93100C00EDCCB9 /* Library.framework in Frameworks */, - 191A4B4628FF3965009D62A5 /* ReactiveSwift in Frameworks */, - 191A4B4428FF395E009D62A5 /* ReactiveExtensions in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3216,16 +3219,11 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 19BF226528D10497007F4197 /* FirebasePerformance in Frameworks */, - 19BF226128D10497007F4197 /* FirebaseAnalytics in Frameworks */, 60EAD1C728D25A36009F9474 /* AppCenterDistribute in Frameworks */, 06EA2D4C280F76B700F4DE2E /* Prelude in Frameworks */, A73924001D27230B004524C3 /* Kickstarter_Framework.framework in Frameworks */, 19821C85299D392400EF312F /* AppboySegment in Frameworks */, 191EDC6728E29BB9009B41B2 /* PerimeterX in Frameworks */, - 19BF226328D10497007F4197 /* FirebaseCrashlytics in Frameworks */, - 1905787B28F8CD2500428375 /* ReactiveSwift in Frameworks */, - 1998BCA828F60E8900D04077 /* ReactiveExtensions in Frameworks */, D0B45B6B1EF858C00020A8DA /* KsApi.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3234,8 +3232,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 70495690299D53ED00B273DF /* SnapshotTesting in Frameworks */, A724BA641D2BFCC80041863C /* Kickstarter_Framework.framework in Frameworks */, + 70495690299D53ED00B273DF /* SnapshotTesting in Frameworks */, 1998BCB028F60EC300D04077 /* ReactiveExtensions-TestHelpers in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3249,20 +3247,11 @@ 06634FC02807A4C300950F60 /* ApolloAPI in Frameworks */, 06634FC22807A4C300950F60 /* ApolloUtils in Frameworks */, 1998BCB228F60ED400D04077 /* ReactiveExtensions in Frameworks */, - 1905788128F8CD7000428375 /* ReactiveSwift in Frameworks */, 60DA511428C96A65002E2DF1 /* SwiftSoup in Frameworks */, 198E574D28E2705E00D5B8A9 /* PerimeterX in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - D01587551EEB2DE4006E7684 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - D01587591EEB2DE4006E7684 /* KsApi.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -4831,6 +4820,18 @@ path = Features; sourceTree = ""; }; + 19A666622A0472C700708F8B /* RemoteConfig */ = { + isa = PBXGroup; + children = ( + 19A666602A0472A500708F8B /* RemoteConfligClient.swift */, + 606C45F029FACD49001BA067 /* RemoteConfigClientType.swift */, + 606C45F229FACD68001BA067 /* RemoteConfigFeature.swift */, + 606C45F429FACD78001BA067 /* RemoteConfigFeature+Helpers.swift */, + 606C45F629FACD94001BA067 /* RemoteConfigFeature+HelpersTests.swift */, + ); + path = RemoteConfig; + sourceTree = ""; + }; 19A97CD428C79BF60031B857 /* Comments */ = { isa = PBXGroup; children = ( @@ -6242,6 +6243,7 @@ 8A3BF51D23F5DB52002AD818 /* MockCoreTelephonyNetworkInfo.swift */, D093B4B721A8B0E000910962 /* MockPushRegistration.swift */, 774D98DB23B1520D00FC81C2 /* MockOptimizelyClient.swift */, + 606C45F929FACE17001BA067 /* MockRemoteConfigClient.swift */, 80E26A121D500C6A007B3022 /* Navigation.swift */, 77D19FF4240813240058FC8E /* NavigationController.swift */, A7ED1F1B1E830FDC00BFFA01 /* NavigationTests.swift */, @@ -6283,6 +6285,7 @@ 8053D3101D3848A3007B85DB /* Reachability.swift */, A7DC83951C9DBBE700BB2B44 /* RefTag.swift */, A7ED1F1E1E830FDC00BFFA01 /* RefTagTests.swift */, + 19A666622A0472C700708F8B /* RemoteConfig */, D7ADDFE522E0DAEB00157D83 /* RewardCellProjectBackingStateType.swift */, D7180BA022EF9DD900EB0110 /* RewardCellProjectBackingStateTypeTests.swift */, 0169F8C01D6CA27500C8D5C5 /* RootCategory.swift */, @@ -7201,9 +7204,11 @@ 606754BC28CF91D60033CD5E /* FacebookCore */, 606754BE28CF91DD0033CD5E /* FacebookLogin */, 198E574A28E2705100D5B8A9 /* PerimeterX */, - 191A4B3F28FF386C009D62A5 /* ReactiveSwift */, - 191A4B4128FF3897009D62A5 /* ReactiveExtensions */, 19821C86299D3E8300EF312F /* AppboySegment */, + 6078106A2A0419130050D4F7 /* FirebaseCrashlytics */, + 6078106C2A0419170050D4F7 /* FirebasePerformance */, + 6078106E2A04191C0050D4F7 /* FirebaseAnalytics */, + 19A6665E2A045F2A00708F8B /* FirebaseRemoteConfig */, ); productName = "Library-iOS"; productReference = A755113C1C8642B3005355CF /* Library.framework */; @@ -7227,7 +7232,6 @@ packageProductDependencies = ( 19F91B13289C8097000AEC6A /* Stripe */, 191A4B4B28FF3AF8009D62A5 /* ReactiveExtensions-TestHelpers */, - 191A4B4F28FF44D8009D62A5 /* ReactiveExtensions */, ); productName = "Library-iOSTests"; productReference = A75511451C8642B3005355CF /* Library-iOSTests.xctest */; @@ -7249,8 +7253,6 @@ ); name = "Kickstarter-Framework-iOS"; packageProductDependencies = ( - 191A4B4328FF395E009D62A5 /* ReactiveExtensions */, - 191A4B4528FF3965009D62A5 /* ReactiveSwift */, ); productName = "Kickstarter-iOS-Framework"; productReference = A7C7959E1C873A870081977F /* Kickstarter_Framework.framework */; @@ -7275,13 +7277,8 @@ name = "Kickstarter-iOS"; packageProductDependencies = ( 06EA2D4B280F76B700F4DE2E /* Prelude */, - 19BF226028D10497007F4197 /* FirebaseAnalytics */, - 19BF226228D10497007F4197 /* FirebaseCrashlytics */, - 19BF226428D10497007F4197 /* FirebasePerformance */, 60EAD1C628D25A36009F9474 /* AppCenterDistribute */, 191EDC6628E29BB9009B41B2 /* PerimeterX */, - 1998BCA728F60E8900D04077 /* ReactiveExtensions */, - 1905787A28F8CD2500428375 /* ReactiveSwift */, 19821C84299D392400EF312F /* AppboySegment */, ); productName = Kickstarter; @@ -7334,7 +7331,6 @@ 60DA511328C96A65002E2DF1 /* SwiftSoup */, 198E574C28E2705E00D5B8A9 /* PerimeterX */, 1998BCB128F60ED400D04077 /* ReactiveExtensions */, - 1905788028F8CD7000428375 /* ReactiveSwift */, ); productName = KsApi; productReference = D01587501EEB2DE4006E7684 /* KsApi.framework */; @@ -7345,7 +7341,6 @@ buildConfigurationList = D01587651EEB2DE4006E7684 /* Build configuration list for PBXNativeTarget "KsApiTests" */; buildPhases = ( D01587541EEB2DE4006E7684 /* Sources */, - D01587551EEB2DE4006E7684 /* Frameworks */, D01587561EEB2DE4006E7684 /* Resources */, ); buildRules = ( @@ -7440,7 +7435,6 @@ 60EAD1C528D25A36009F9474 /* XCRemoteSwiftPackageReference "appcenter-sdk-apple" */, 602C97D628DB787900919CA8 /* XCRemoteSwiftPackageReference "px-iOS-Framework" */, 194C593E28F5E7FF00453249 /* XCRemoteSwiftPackageReference "Kickstarter-ReactiveExtensions" */, - 1905787928F8CD2500428375 /* XCRemoteSwiftPackageReference "ReactiveSwift" */, 7049568E299D53ED00B273DF /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */, 19821C83299D392300EF312F /* XCRemoteSwiftPackageReference "appboy-segment-ios" */, ); @@ -7669,6 +7663,7 @@ A73378FE1D0AE36400C91445 /* LoginStyles.swift in Sources */, 0154A93B1CA1A17800DB9BA4 /* UIColor.swift in Sources */, 8A23203125B0ECF700B940C3 /* IdentifyingTrackingClient.swift in Sources */, + 606C45F329FACD68001BA067 /* RemoteConfigFeature.swift in Sources */, A7F441CD1D005A9400FE6FC5 /* MessagesViewModel.swift in Sources */, 8053D3111D3848A3007B85DB /* Reachability.swift in Sources */, 77C9122723C4F99400F3D2C9 /* Double+Currency.swift in Sources */, @@ -7681,6 +7676,7 @@ 3772A4C6229C9E2000EDDC6F /* String+Attributed.swift in Sources */, A755115B1C8642C3005355CF /* AppEnvironment.swift in Sources */, 77EBB62123A175C700A58853 /* RootViewModel.swift in Sources */, + 19A666612A0472A500708F8B /* RemoteConfligClient.swift in Sources */, A7F441AD1D005A9400FE6FC5 /* ActivitiesViewModel.swift in Sources */, D660078C2416D66C00AC1EDB /* CuratedProjectsViewModel.swift in Sources */, 77C93C2522C27443005D3195 /* DebugData.swift in Sources */, @@ -7727,6 +7723,7 @@ 59673CBF1D50EE9B0035AFD9 /* VideoViewModel.swift in Sources */, 9D10B91B1D35407C008B8045 /* String+Truncate.swift in Sources */, 8AD48633235939AF00A1463E /* StripeTypes.swift in Sources */, + 606C45F129FACD49001BA067 /* RemoteConfigClientType.swift in Sources */, A72E75971CC313A400983066 /* ValueCell.swift in Sources */, 7705563B22BC03A600DCB062 /* TableViewStyles.swift in Sources */, D6534D3A22E7878D00E9D279 /* CreditCard+Utils.swift in Sources */, @@ -7798,6 +7795,7 @@ A75C811E1D210C4700B5AD03 /* ProjectActivityItemProvider.swift in Sources */, 370C8B6623590CA500DE75DD /* LoadingButtonViewModel.swift in Sources */, 1611EF5E23ABD1550051CDCC /* OptimizelyResultType.swift in Sources */, + 606C45FA29FACE17001BA067 /* MockRemoteConfigClient.swift in Sources */, D79F0F7321067FB500D3B32C /* SettingsRecommendationsCellViewModel.swift in Sources */, D04AAC29218BB70D00CF713E /* BetaToolsViewModel.swift in Sources */, D093B49C21A86FD800910962 /* PushRegistration.swift in Sources */, @@ -7941,6 +7939,7 @@ A7F441C11D005A9400FE6FC5 /* DiscoveryViewModel.swift in Sources */, A78537E21CB5422100385B73 /* UIScreenType.swift in Sources */, 19462F6B29D4893500868694 /* ChangeEmailViewModelSwiftUIIntegrationTest.swift in Sources */, + 606C45F529FACD78001BA067 /* RemoteConfigFeature+Helpers.swift in Sources */, 598D96C21D429756003F3F66 /* ActivitySampleStyles.swift in Sources */, 8AE8D86623466EB9005860C6 /* UpdateBackingInput+Constructor.swift in Sources */, 59B0E07E1D147F340081D2DC /* DashboardStyles.swift in Sources */, @@ -8051,6 +8050,7 @@ A7ED1FC61E831C5C00BFFA01 /* SurveyResponseViewModelTests.swift in Sources */, 061645AE26697D55007D8D96 /* CommentRepliesViewModelTests.swift in Sources */, A7ED1F281E830FDC00BFFA01 /* CircleAvatarImageViewTests.swift in Sources */, + 606C45F829FACD9F001BA067 /* RemoteConfigFeature+HelpersTests.swift in Sources */, A7ED1FB81E831C5C00BFFA01 /* BackingCellViewModelTests.swift in Sources */, 1923770A28DA2AE300F68635 /* Stripe+PaymentMethod.swift in Sources */, A7ED1FFD1E831C5C00BFFA01 /* ProjectUpdatesViewModelTests.swift in Sources */, @@ -10362,17 +10362,9 @@ 06634FC32807A4EB00950F60 /* XCRemoteSwiftPackageReference "Kickstarter-Prelude" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/kickstarter/Kickstarter-Prelude.git"; - requirement = { - branch = "feature/swift-package"; - kind = branch; - }; - }; - 1905787928F8CD2500428375 /* XCRemoteSwiftPackageReference "ReactiveSwift" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/ReactiveCocoa/ReactiveSwift"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 6.5.0; + minimumVersion = 1.0.0; }; }; 194520C12888542100CA9B88 /* XCRemoteSwiftPackageReference "stripe-ios" */ = { @@ -10387,8 +10379,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/kickstarter/Kickstarter-ReactiveExtensions"; requirement = { - branch = "feature/swift-package"; - kind = branch; + kind = upToNextMajorVersion; + minimumVersion = 2.0.0; }; }; 19821C83299D392300EF312F /* XCRemoteSwiftPackageReference "appboy-segment-ios" */ = { @@ -10404,7 +10396,7 @@ repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 9.0.0; + minimumVersion = 10.7.0; }; }; 602C97D628DB787900919CA8 /* XCRemoteSwiftPackageReference "px-iOS-Framework" */ = { @@ -10496,46 +10488,11 @@ package = 06634FC32807A4EB00950F60 /* XCRemoteSwiftPackageReference "Kickstarter-Prelude" */; productName = Prelude; }; - 1905787A28F8CD2500428375 /* ReactiveSwift */ = { - isa = XCSwiftPackageProductDependency; - package = 1905787928F8CD2500428375 /* XCRemoteSwiftPackageReference "ReactiveSwift" */; - productName = ReactiveSwift; - }; - 1905788028F8CD7000428375 /* ReactiveSwift */ = { - isa = XCSwiftPackageProductDependency; - package = 1905787928F8CD2500428375 /* XCRemoteSwiftPackageReference "ReactiveSwift" */; - productName = ReactiveSwift; - }; - 191A4B3F28FF386C009D62A5 /* ReactiveSwift */ = { - isa = XCSwiftPackageProductDependency; - package = 1905787928F8CD2500428375 /* XCRemoteSwiftPackageReference "ReactiveSwift" */; - productName = ReactiveSwift; - }; - 191A4B4128FF3897009D62A5 /* ReactiveExtensions */ = { - isa = XCSwiftPackageProductDependency; - package = 194C593E28F5E7FF00453249 /* XCRemoteSwiftPackageReference "Kickstarter-ReactiveExtensions" */; - productName = ReactiveExtensions; - }; - 191A4B4328FF395E009D62A5 /* ReactiveExtensions */ = { - isa = XCSwiftPackageProductDependency; - package = 194C593E28F5E7FF00453249 /* XCRemoteSwiftPackageReference "Kickstarter-ReactiveExtensions" */; - productName = ReactiveExtensions; - }; - 191A4B4528FF3965009D62A5 /* ReactiveSwift */ = { - isa = XCSwiftPackageProductDependency; - package = 1905787928F8CD2500428375 /* XCRemoteSwiftPackageReference "ReactiveSwift" */; - productName = ReactiveSwift; - }; 191A4B4B28FF3AF8009D62A5 /* ReactiveExtensions-TestHelpers */ = { isa = XCSwiftPackageProductDependency; package = 194C593E28F5E7FF00453249 /* XCRemoteSwiftPackageReference "Kickstarter-ReactiveExtensions" */; productName = "ReactiveExtensions-TestHelpers"; }; - 191A4B4F28FF44D8009D62A5 /* ReactiveExtensions */ = { - isa = XCSwiftPackageProductDependency; - package = 194C593E28F5E7FF00453249 /* XCRemoteSwiftPackageReference "Kickstarter-ReactiveExtensions" */; - productName = ReactiveExtensions; - }; 191EDC6628E29BB9009B41B2 /* PerimeterX */ = { isa = XCSwiftPackageProductDependency; package = 602C97D628DB787900919CA8 /* XCRemoteSwiftPackageReference "px-iOS-Framework" */; @@ -10566,11 +10523,6 @@ package = 602C97D628DB787900919CA8 /* XCRemoteSwiftPackageReference "px-iOS-Framework" */; productName = PerimeterX; }; - 1998BCA728F60E8900D04077 /* ReactiveExtensions */ = { - isa = XCSwiftPackageProductDependency; - package = 194C593E28F5E7FF00453249 /* XCRemoteSwiftPackageReference "Kickstarter-ReactiveExtensions" */; - productName = ReactiveExtensions; - }; 1998BCAF28F60EC300D04077 /* ReactiveExtensions-TestHelpers */ = { isa = XCSwiftPackageProductDependency; package = 194C593E28F5E7FF00453249 /* XCRemoteSwiftPackageReference "Kickstarter-ReactiveExtensions" */; @@ -10581,20 +10533,10 @@ package = 194C593E28F5E7FF00453249 /* XCRemoteSwiftPackageReference "Kickstarter-ReactiveExtensions" */; productName = ReactiveExtensions; }; - 19BF226028D10497007F4197 /* FirebaseAnalytics */ = { - isa = XCSwiftPackageProductDependency; - package = 19BF225F28D10497007F4197 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; - productName = FirebaseAnalytics; - }; - 19BF226228D10497007F4197 /* FirebaseCrashlytics */ = { + 19A6665E2A045F2A00708F8B /* FirebaseRemoteConfig */ = { isa = XCSwiftPackageProductDependency; package = 19BF225F28D10497007F4197 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; - productName = FirebaseCrashlytics; - }; - 19BF226428D10497007F4197 /* FirebasePerformance */ = { - isa = XCSwiftPackageProductDependency; - package = 19BF225F28D10497007F4197 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; - productName = FirebasePerformance; + productName = FirebaseRemoteConfig; }; 19F91B13289C8097000AEC6A /* Stripe */ = { isa = XCSwiftPackageProductDependency; @@ -10611,6 +10553,21 @@ package = 606754B728CF8A190033CD5E /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */; productName = FacebookLogin; }; + 6078106A2A0419130050D4F7 /* FirebaseCrashlytics */ = { + isa = XCSwiftPackageProductDependency; + package = 19BF225F28D10497007F4197 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseCrashlytics; + }; + 6078106C2A0419170050D4F7 /* FirebasePerformance */ = { + isa = XCSwiftPackageProductDependency; + package = 19BF225F28D10497007F4197 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebasePerformance; + }; + 6078106E2A04191C0050D4F7 /* FirebaseAnalytics */ = { + isa = XCSwiftPackageProductDependency; + package = 19BF225F28D10497007F4197 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseAnalytics; + }; 60DA50FD28C38DDB002E2DF1 /* AlamofireImage */ = { isa = XCSwiftPackageProductDependency; package = 60DA50F628BFA331002E2DF1 /* XCRemoteSwiftPackageReference "AlamofireImage" */; diff --git a/Kickstarter.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Kickstarter.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f073314883..c687341324 100644 --- a/Kickstarter.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Kickstarter.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,12 +1,12 @@ { "pins" : [ { - "identity" : "abseil-cpp-swiftpm", + "identity" : "abseil-cpp-binary", "kind" : "remoteSourceControl", - "location" : "https://github.com/firebase/abseil-cpp-SwiftPM.git", + "location" : "https://github.com/google/abseil-cpp-binary.git", "state" : { - "revision" : "583de9bd60f66b40e78d08599cc92036c2e7e4e1", - "version" : "0.20220203.2" + "revision" : "a5f16ba68913840ee5df91b8dc06f5cc063579de", + "version" : "1.2021110200.0" } }, { @@ -63,15 +63,6 @@ "version" : "4.4.3" } }, - { - "identity" : "boringssl-swiftpm", - "kind" : "remoteSourceControl", - "location" : "https://github.com/firebase/boringssl-SwiftPM.git", - "state" : { - "revision" : "dd3eda2b05a3f459fc3073695ad1b28659066eab", - "version" : "0.9.1" - } - }, { "identity" : "braze-ios-sdk", "kind" : "remoteSourceControl", @@ -95,8 +86,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/firebase-ios-sdk.git", "state" : { - "revision" : "7e80c25b51c2ffa238879b07fbfc5baa54bb3050", - "version" : "9.6.0" + "revision" : "4961c0b7eb5d794e47a58dbfdd258b4beca4263a", + "version" : "10.9.0" } }, { @@ -104,8 +95,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleAppMeasurement.git", "state" : { - "revision" : "c1cfde8067668027b23a42c29d11c246152fe046", - "version" : "9.6.0" + "revision" : "9209b95a2593985569918e5e5ee2bf4ef8ff3640", + "version" : "10.9.0" } }, { @@ -122,17 +113,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleUtilities.git", "state" : { - "revision" : "22907832079d808e82f1182b21af58ef3880666f", - "version" : "7.8.0" + "revision" : "871d43135925cde39ef7421d8723ce47edfdcc39", + "version" : "7.11.1" } }, { - "identity" : "grpc-ios", + "identity" : "grpc-binary", "kind" : "remoteSourceControl", - "location" : "https://github.com/grpc/grpc-ios.git", + "location" : "https://github.com/google/grpc-binary.git", "state" : { - "revision" : "8440b914756e0d26d4f4d054a1c1581daedfc5b6", - "version" : "1.44.3-grpc" + "revision" : "df37f6af8a273bc687e3166843ed86007de57d78", + "version" : "1.44.0" } }, { @@ -158,8 +149,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/kickstarter/Kickstarter-Prelude.git", "state" : { - "branch" : "feature/swift-package", - "revision" : "b0cec69c19a13088864b52ad1745397f587daec7" + "revision" : "f3a2dc432e8d5fea7acddb90d57adb2e9d421f7e", + "version" : "1.0.0" } }, { @@ -167,8 +158,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/kickstarter/Kickstarter-ReactiveExtensions", "state" : { - "branch" : "feature/swift-package", - "revision" : "34276be97fa5acae36e714ad65f8ecb733c0566b" + "revision" : "36984ccbdbf1874728f801f29bc0e41706efdd0a", + "version" : "2.0.0" } }, { @@ -239,8 +230,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/ReactiveCocoa/ReactiveSwift", "state" : { - "revision" : "e03cda84105ba707de039b757e2b2de868c65a3e", - "version" : "6.5.0" + "revision" : "40c465af19b993344e84355c00669ba2022ca3cd", + "version" : "7.1.1" } }, { diff --git a/Library/AppEnvironment.swift b/Library/AppEnvironment.swift index e0ad96f1a6..6a6efc80ae 100644 --- a/Library/AppEnvironment.swift +++ b/Library/AppEnvironment.swift @@ -73,6 +73,12 @@ public struct AppEnvironment: AppEnvironmentType { ) } + public static func updateRemoteConfigClient(_ remoteConfigClient: RemoteConfigClientType?) { + self.replaceCurrentEnvironment( + remoteConfigClient: remoteConfigClient + ) + } + public static func updateServerConfig(_ config: ServerConfigType) { let service = Service(serverConfig: config) @@ -170,6 +176,7 @@ public struct AppEnvironment: AppEnvironmentType { optimizelyClient: OptimizelyClientType? = AppEnvironment.current.optimizelyClient, pushRegistrationType: PushRegistrationType.Type = AppEnvironment.current.pushRegistrationType, reachability: SignalProducer = AppEnvironment.current.reachability, + remoteConfigClient: RemoteConfigClientType? = AppEnvironment.current.remoteConfigClient, scheduler: DateScheduler = AppEnvironment.current.scheduler, ubiquitousStore: KeyValueStoreType = AppEnvironment.current.ubiquitousStore, userDefaults: KeyValueStoreType = AppEnvironment.current.userDefaults, @@ -203,6 +210,7 @@ public struct AppEnvironment: AppEnvironmentType { optimizelyClient: optimizelyClient, pushRegistrationType: pushRegistrationType, reachability: reachability, + remoteConfigClient: remoteConfigClient, scheduler: scheduler, ubiquitousStore: ubiquitousStore, userDefaults: userDefaults, @@ -241,6 +249,7 @@ public struct AppEnvironment: AppEnvironmentType { optimizelyClient: OptimizelyClientType? = AppEnvironment.current.optimizelyClient, pushRegistrationType: PushRegistrationType.Type = AppEnvironment.current.pushRegistrationType, reachability: SignalProducer = AppEnvironment.current.reachability, + remoteConfigClient: RemoteConfigClientType? = AppEnvironment.current.remoteConfigClient, scheduler: DateScheduler = AppEnvironment.current.scheduler, ubiquitousStore: KeyValueStoreType = AppEnvironment.current.ubiquitousStore, userDefaults: KeyValueStoreType = AppEnvironment.current.userDefaults, @@ -275,6 +284,7 @@ public struct AppEnvironment: AppEnvironmentType { optimizelyClient: optimizelyClient, pushRegistrationType: pushRegistrationType, reachability: reachability, + remoteConfigClient: remoteConfigClient, scheduler: scheduler, ubiquitousStore: ubiquitousStore, userDefaults: userDefaults, diff --git a/Library/Environment.swift b/Library/Environment.swift index 9f267feb3f..454a6224e2 100644 --- a/Library/Environment.swift +++ b/Library/Environment.swift @@ -99,6 +99,9 @@ public struct Environment { /// A reachability signal producer. public let reachability: SignalProducer + /// The remote config client + public let remoteConfigClient: RemoteConfigClientType? + /// A scheduler to use for all time-based RAC operators. Default value is /// `QueueScheduler.mainQueueScheduler`. public let scheduler: DateScheduler @@ -141,6 +144,7 @@ public struct Environment { optimizelyClient: OptimizelyClientType? = nil, pushRegistrationType: PushRegistrationType.Type = PushRegistration.self, reachability: SignalProducer = Reachability.signalProducer, + remoteConfigClient: RemoteConfigClientType? = nil, scheduler: DateScheduler = QueueScheduler.main, ubiquitousStore: KeyValueStoreType = NSUbiquitousKeyValueStore.default, userDefaults: KeyValueStoreType = UserDefaults.standard, @@ -174,6 +178,7 @@ public struct Environment { self.optimizelyClient = optimizelyClient self.pushRegistrationType = pushRegistrationType self.reachability = reachability + self.remoteConfigClient = remoteConfigClient self.scheduler = scheduler self.ubiquitousStore = ubiquitousStore self.userDefaults = userDefaults diff --git a/Library/KeyValueStoreType.swift b/Library/KeyValueStoreType.swift index b6ae93b369..27226e551d 100644 --- a/Library/KeyValueStoreType.swift +++ b/Library/KeyValueStoreType.swift @@ -13,6 +13,7 @@ public enum AppKeys: String { case lastSeenActivitySampleId = "com.kickstarter.KeyValueStoreType.lastSeenActivitySampleId" case onboardingCategories = "com.kickstarter.KeyValueStoreType.onboardingCategories" case optimizelyFeatureFlags = "com.kickstarter.KeyValueStoreType.optimizelyFeatureFlags" + case remoteConfigFeatureFlags = "com.kickstarter.KeyValueStoreType.remoteConfigFeatureFlags" case seenAppRating = "com.kickstarter.KeyValueStoreType.hasSeenAppRating" case seenGamesNewsletter = "com.kickstarter.KeyValueStoreType.hasSeenGamesNewsletter" // swiftformat:enable wrap @@ -45,6 +46,7 @@ public protocol KeyValueStoreType: AnyObject { var lastSeenActivitySampleId: Int { get set } var onboardingCategories: Data? { get set } var optimizelyFeatureFlags: [String: Bool] { get set } + var remoteConfigFeatureFlags: [String: Bool] { get set } } extension KeyValueStoreType { @@ -166,6 +168,16 @@ extension KeyValueStoreType { self.set(newValue, forKey: AppKeys.optimizelyFeatureFlags.rawValue) } } + + public var remoteConfigFeatureFlags: [String: Bool] { + get { + return self + .object(forKey: AppKeys.remoteConfigFeatureFlags.rawValue) as? [String: Bool] ?? [:] + } + set { + self.set(newValue, forKey: AppKeys.remoteConfigFeatureFlags.rawValue) + } + } } extension UserDefaults: KeyValueStoreType {} diff --git a/Library/MockRemoteConfigClient.swift b/Library/MockRemoteConfigClient.swift new file mode 100644 index 0000000000..6392259b0a --- /dev/null +++ b/Library/MockRemoteConfigClient.swift @@ -0,0 +1,39 @@ +import FirebaseRemoteConfig +import Foundation + +private class MockRemoteConfigValue: RemoteConfigValue { + var bool = false +} + +public class MockRemoteConfigClient: RemoteConfigClientType { + public var features: [String: Bool] + + public init() { + self.features = [:] + } + + public func activate(completion _: ((Bool, Error?) -> Void)?) {} + + public func configValue(forKey key: String?) -> RemoteConfigValue { + let value = MockRemoteConfigValue() + + guard let keyValue = key else { + return value + } + + value.bool = self.features[keyValue] == true + + return value + } + + public func fetch(completionHandler _: ((RemoteConfigFetchStatus, Error?) -> Void)?) {} + + public func setDefaults(_: [String: NSObject]?) {} + + public func addOnConfigUpdateListener(remoteConfigUpdateCompletion _: @escaping ( + RemoteConfigUpdate?, + Error? + ) -> Void) -> ConfigUpdateListenerRegistration { + return ConfigUpdateListenerRegistration() + } +} diff --git a/Library/Notifications.swift b/Library/Notifications.swift index f2eeabeb87..7071fce2c1 100644 --- a/Library/Notifications.swift +++ b/Library/Notifications.swift @@ -6,9 +6,9 @@ public enum CurrentUserNotifications { public static let environmentChanged = "CurrentUserNotification.environmentChanged" public static let localePreferencesChanged = "CurrentUserNotification.localePreferencesChanged" public static let onboardingCompleted = "CurrentUserNotifications.onboardingCompleted" - public static let optimizelyClientConfigured = "CurrentUserNotification.optimizelyClientConfigured" - public static let optimizelyClientConfigurationFailed = - "CurrentUserNotification.optimizelyClientConfigurationFailed" + public static let remoteConfigClientConfigured = "CurrentUserNotification.remoteConfigClientConfigured" + public static let remoteConfigClientConfigurationFailed = + "CurrentUserNotification.remoteConfigClientConfigurationFailed" public static let projectBacked = "CurrentUserNotifications.projectBacked" public static let projectSaved = "CurrentUserNotifications.projectSaved" public static let recommendationsSettingChanged = "CurrentUserNotifications.recommendationsSettingChanged" @@ -40,11 +40,11 @@ extension Notification.Name { public static let ksr_onboardingCompleted = Notification.Name( rawValue: CurrentUserNotifications.onboardingCompleted ) - public static let ksr_optimizelyClientConfigured = Notification.Name( - rawValue: CurrentUserNotifications.optimizelyClientConfigured + public static let ksr_remoteConfigClientConfigured = Notification.Name( + rawValue: CurrentUserNotifications.remoteConfigClientConfigured ) - public static let ksr_optimizelyClientConfigurationFailed = Notification.Name( - rawValue: CurrentUserNotifications.optimizelyClientConfigurationFailed + public static let ksr_remoteConfigClientConfigurationFailed = Notification.Name( + rawValue: CurrentUserNotifications.remoteConfigClientConfigurationFailed ) public static let ksr_projectBacked = Notification.Name(rawValue: CurrentUserNotifications.projectBacked) public static let ksr_projectSaved = Notification.Name(rawValue: CurrentUserNotifications.projectSaved) diff --git a/Library/OptimizelyFeature+Helpers.swift b/Library/OptimizelyFeature+Helpers.swift index cce93d570b..2c50eae91d 100644 --- a/Library/OptimizelyFeature+Helpers.swift +++ b/Library/OptimizelyFeature+Helpers.swift @@ -27,17 +27,3 @@ public func featureSettingsPaymentSheetEnabled() -> Bool { (AppEnvironment.current.optimizelyClient? .isFeatureEnabled(featureKey: OptimizelyFeature.settingsPaymentSheetEnabled.rawValue) ?? false) } - -public func featureFacebookLoginDeprecationEnabled() -> Bool { - return AppEnvironment.current.userDefaults - .optimizelyFeatureFlags[OptimizelyFeature.facebookLoginDeprecationEnabled.rawValue] ?? - (AppEnvironment.current.optimizelyClient? - .isFeatureEnabled(featureKey: OptimizelyFeature.facebookLoginDeprecationEnabled.rawValue) ?? false) -} - -public func featureConsentManagementDialogEnabled() -> Bool { - return AppEnvironment.current.userDefaults - .optimizelyFeatureFlags[OptimizelyFeature.consentManagementDialogEnabled.rawValue] ?? - (AppEnvironment.current.optimizelyClient? - .isFeatureEnabled(featureKey: OptimizelyFeature.consentManagementDialogEnabled.rawValue) ?? false) -} diff --git a/Library/OptimizelyFeature+HelpersTests.swift b/Library/OptimizelyFeature+HelpersTests.swift index c711f0d348..129705efdc 100644 --- a/Library/OptimizelyFeature+HelpersTests.swift +++ b/Library/OptimizelyFeature+HelpersTests.swift @@ -65,31 +65,4 @@ final class OptimizelyFeatureHelpersTests: TestCase { XCTAssertFalse(featureSettingsPaymentSheetEnabled()) } } - - func testFacebookDeprecation_Optimizely_FeatureFlag_False() { - let mockOptimizelyClient = MockOptimizelyClient() - |> \.features .~ [OptimizelyFeature.facebookLoginDeprecationEnabled.rawValue: false] - - withEnvironment(optimizelyClient: mockOptimizelyClient) { - XCTAssertFalse(featureFacebookLoginDeprecationEnabled()) - } - } - - func testConsentManagementDialog_Optimizely_FeatureFlag_True() { - let mockOptimizelyClient = MockOptimizelyClient() - |> \.features .~ [OptimizelyFeature.consentManagementDialogEnabled.rawValue: true] - - withEnvironment(optimizelyClient: mockOptimizelyClient) { - XCTAssertTrue(featureConsentManagementDialogEnabled()) - } - } - - func testConsentManagementDialog_Optimizely_FeatureFlag_False() { - let mockOptimizelyClient = MockOptimizelyClient() - |> \.features .~ [OptimizelyFeature.consentManagementDialogEnabled.rawValue: false] - - withEnvironment(optimizelyClient: mockOptimizelyClient) { - XCTAssertFalse(featureConsentManagementDialogEnabled()) - } - } } diff --git a/Library/RemoteConfig/RemoteConfigClientType.swift b/Library/RemoteConfig/RemoteConfigClientType.swift new file mode 100644 index 0000000000..fc38478b11 --- /dev/null +++ b/Library/RemoteConfig/RemoteConfigClientType.swift @@ -0,0 +1,25 @@ +import FirebaseRemoteConfig +import Foundation + +public protocol RemoteConfigClientType: AnyObject { + func activate(completion: ((Bool, Error?) -> Void)?) + func fetch(completionHandler: ((RemoteConfigFetchStatus, Error?) -> Void)?) + func setDefaults(_ defaults: [String: NSObject]?) + func addOnConfigUpdateListener(remoteConfigUpdateCompletion listener: @escaping ( + RemoteConfigUpdate?, + Error? + ) -> Void) -> ConfigUpdateListenerRegistration + func configValue(forKey key: String?) -> RemoteConfigValue +} + +extension RemoteConfigClientType { + /* Returns all features the app knows about */ + + public func allFeatures() -> [RemoteConfigFeature] { + return RemoteConfigFeature.allCases + } + + public func isFeatureEnabled(featureKey: RemoteConfigFeature) -> Bool { + self.configValue(forKey: featureKey.rawValue).boolValue == true + } +} diff --git a/Library/RemoteConfig/RemoteConfigFeature+Helpers.swift b/Library/RemoteConfig/RemoteConfigFeature+Helpers.swift new file mode 100644 index 0000000000..d21b0e680f --- /dev/null +++ b/Library/RemoteConfig/RemoteConfigFeature+Helpers.swift @@ -0,0 +1,15 @@ +/// Return remote config values either a value from the cloud, if it found one, or a default value based on the provided key + +public func featureConsentManagementDialogEnabled() -> Bool { + return AppEnvironment.current.userDefaults + .remoteConfigFeatureFlags[RemoteConfigFeature.consentManagementDialogEnabled.rawValue] ?? + (AppEnvironment.current.remoteConfigClient? + .isFeatureEnabled(featureKey: RemoteConfigFeature.consentManagementDialogEnabled) ?? false) +} + +public func featureFacebookLoginInterstitialEnabled() -> Bool { + return AppEnvironment.current.userDefaults + .remoteConfigFeatureFlags[RemoteConfigFeature.facebookLoginInterstitialEnabled.rawValue] ?? + (AppEnvironment.current.remoteConfigClient? + .isFeatureEnabled(featureKey: RemoteConfigFeature.facebookLoginInterstitialEnabled) ?? false) +} diff --git a/Library/RemoteConfig/RemoteConfigFeature+HelpersTests.swift b/Library/RemoteConfig/RemoteConfigFeature+HelpersTests.swift new file mode 100644 index 0000000000..3b253cbbbe --- /dev/null +++ b/Library/RemoteConfig/RemoteConfigFeature+HelpersTests.swift @@ -0,0 +1,43 @@ +@testable import Library +import Prelude +import XCTest + +final class RemoteConfigFeatureHelpersTests: TestCase { + func testConsentManagementDialog_RemoteConfig_FeatureFlag_False() { + let mockRemoteConfigClient = MockRemoteConfigClient() + |> \.features .~ [RemoteConfigFeature.consentManagementDialogEnabled.rawValue: false] + + withEnvironment(remoteConfigClient: mockRemoteConfigClient) { + XCTAssertFalse(featureConsentManagementDialogEnabled()) + } + } + + /** FIXME: RemoteConfigValue is not initializing because its' OBJC intiliazer is not available + func testConsentManagementDialog_RemoteConfig_FeatureFlag_True() { + let mockRemoteConfigClient = MockRemoteConfigClient() + |> \.features .~ [RemoteConfigFeature.consentManagementDialogEnabled.rawValue: true] + + withEnvironment(remoteConfigClient: mockRemoteConfigClient) { + XCTAssertTrue(featureConsentManagementDialogEnabled()) + } + } + + func testFacebookDeprecation_RemoteConfig_FeatureFlag_True() { + let mockRemoteConfigClient = MockRemoteConfigClient() + |> \.features .~ [RemoteConfigFeature.facebookLoginInterstitialEnabled.rawValue: true] + + withEnvironment(remoteConfigClient: mockRemoteConfigClient) { + XCTAssertTrue(featureFacebookLoginInterstitialEnabled()) + } + } + */ + + func testFacebookDeprecation_RemoteConfig_FeatureFlag_False() { + let mockRemoteConfigClient = MockRemoteConfigClient() + |> \.features .~ [RemoteConfigFeature.facebookLoginInterstitialEnabled.rawValue: false] + + withEnvironment(remoteConfigClient: mockRemoteConfigClient) { + XCTAssertFalse(featureFacebookLoginInterstitialEnabled()) + } + } +} diff --git a/Library/RemoteConfig/RemoteConfigFeature.swift b/Library/RemoteConfig/RemoteConfigFeature.swift new file mode 100644 index 0000000000..d60d070dc2 --- /dev/null +++ b/Library/RemoteConfig/RemoteConfigFeature.swift @@ -0,0 +1,15 @@ +import Foundation + +public enum RemoteConfigFeature: String, CaseIterable { + case consentManagementDialogEnabled = "consent_management_dialog" + case facebookLoginInterstitialEnabled = "facebook_interstitial" +} + +extension RemoteConfigFeature: CustomStringConvertible { + public var description: String { + switch self { + case .consentManagementDialogEnabled: return "Consent Management Dialog" + case .facebookLoginInterstitialEnabled: return "Facebook Login Interstitial" + } + } +} diff --git a/Library/RemoteConfig/RemoteConfligClient.swift b/Library/RemoteConfig/RemoteConfligClient.swift new file mode 100644 index 0000000000..09f8970e3e --- /dev/null +++ b/Library/RemoteConfig/RemoteConfligClient.swift @@ -0,0 +1,32 @@ +import FirebaseRemoteConfig + +public class RemoteConfigClient: RemoteConfigClientType { + private var sharedClient: RemoteConfig + + public init(with client: RemoteConfig) { + self.sharedClient = client + } + + public func activate(completion: ((Bool, Error?) -> Void)?) { + self.sharedClient.activate(completion: completion) + } + + public func fetch(completionHandler: ((RemoteConfigFetchStatus, Error?) -> Void)?) { + self.sharedClient.fetch(completionHandler: completionHandler) + } + + public func setDefaults(_ defaults: [String: NSObject]?) { + self.sharedClient.setDefaults(defaults) + } + + public func addOnConfigUpdateListener(remoteConfigUpdateCompletion listener: @escaping ( + RemoteConfigUpdate?, + Error? + ) -> Void) -> ConfigUpdateListenerRegistration { + self.sharedClient.addOnConfigUpdateListener(remoteConfigUpdateCompletion: listener) + } + + public func configValue(forKey key: String?) -> RemoteConfigValue { + self.sharedClient.configValue(forKey: key) + } +} diff --git a/Library/TestHelpers/TestCase.swift b/Library/TestHelpers/TestCase.swift index 5e49d9865a..fb55fa3020 100644 --- a/Library/TestHelpers/TestCase.swift +++ b/Library/TestHelpers/TestCase.swift @@ -17,6 +17,7 @@ internal class TestCase: XCTestCase { internal let dateType = MockDate.self internal let mainBundle = MockBundle() internal let optimizelyClient = MockOptimizelyClient() + internal let remoteConfigClient = MockRemoteConfigClient() internal let reachability = MutableProperty(Reachability.wifi) internal let scheduler = TestScheduler(startDate: MockDate().date) internal let segmentTrackingClient = MockTrackingClient() @@ -66,6 +67,7 @@ internal class TestCase: XCTestCase { optimizelyClient: self.optimizelyClient, pushRegistrationType: MockPushRegistration.self, reachability: self.reachability.producer, + remoteConfigClient: self.remoteConfigClient, scheduler: self.scheduler, ubiquitousStore: self.ubiquitousStore, userDefaults: self.userDefaults, diff --git a/Library/TestHelpers/XCTestCase+AppEnvironment.swift b/Library/TestHelpers/XCTestCase+AppEnvironment.swift index b2c0ea496b..5dd9e68710 100644 --- a/Library/TestHelpers/XCTestCase+AppEnvironment.swift +++ b/Library/TestHelpers/XCTestCase+AppEnvironment.swift @@ -38,6 +38,7 @@ extension XCTestCase { mainBundle: NSBundleType = AppEnvironment.current.mainBundle, optimizelyClient: OptimizelyClientType? = AppEnvironment.current.optimizelyClient, pushRegistrationType: PushRegistrationType.Type = AppEnvironment.current.pushRegistrationType, + remoteConfigClient: RemoteConfigClientType? = AppEnvironment.current.remoteConfigClient, scheduler: DateScheduler = AppEnvironment.current.scheduler, ubiquitousStore: KeyValueStoreType = AppEnvironment.current.ubiquitousStore, userDefaults: KeyValueStoreType = AppEnvironment.current.userDefaults, @@ -70,6 +71,7 @@ extension XCTestCase { mainBundle: mainBundle, optimizelyClient: optimizelyClient, pushRegistrationType: pushRegistrationType, + remoteConfigClient: remoteConfigClient, scheduler: scheduler, ubiquitousStore: ubiquitousStore, userDefaults: userDefaults, diff --git a/Library/ViewModels/DiscoveryViewModel.swift b/Library/ViewModels/DiscoveryViewModel.swift index baa5045565..b5fb725437 100644 --- a/Library/ViewModels/DiscoveryViewModel.swift +++ b/Library/ViewModels/DiscoveryViewModel.swift @@ -11,11 +11,11 @@ public protocol DiscoveryViewModelInputs { /// Call when params have been selected. func filter(withParams params: DiscoveryParams) - /// Call when the OptimizelyClient has been configured - func optimizelyClientConfigured() + /// Call when the Remote Config Client has been configured + func remoteConfigClientConfigured() /// Call when the OptimizelyClient configuration has failed - func optimizelyClientConfigurationFailed() + func remoteConfigClientConfigurationFailed() /// Call when the UIPageViewController finishes transitioning. func pageTransition(completed: Bool) @@ -82,16 +82,16 @@ public final class DiscoveryViewModel: DiscoveryViewModelType, DiscoveryViewMode } public init() { - let optimizelyReadyOrContinue = Signal.merge( - self.optimizelyClientConfiguredProperty.signal, + let remoteConfigReadyOrContinue = Signal.merge( + self.remoteConfigClientConfiguredProperty.signal, self.viewDidLoadProperty.signal.map { _ in AppEnvironment.current.optimizelyClient } .ignoreValues(), - self.optimizelyClientConfigurationFailedProperty.signal + self.remoteConfigClientConfigurationFailedProperty.signal ).take(first: 1) let sorts: [DiscoveryParams.Sort] = [.magic, .popular, .newest, .endingSoon] - let configureWithSorts = optimizelyReadyOrContinue.mapConst(sorts) + let configureWithSorts = remoteConfigReadyOrContinue.mapConst(sorts) self.configurePagerDataSource = configureWithSorts self.configureSortPager = configureWithSorts @@ -204,14 +204,14 @@ public final class DiscoveryViewModel: DiscoveryViewModelType, DiscoveryViewMode self.filterWithParamsProperty.value = params } - fileprivate let optimizelyClientConfiguredProperty = MutableProperty(()) - public func optimizelyClientConfigured() { - self.optimizelyClientConfiguredProperty.value = () + fileprivate let remoteConfigClientConfiguredProperty = MutableProperty(()) + public func remoteConfigClientConfigured() { + self.remoteConfigClientConfiguredProperty.value = () } - fileprivate let optimizelyClientConfigurationFailedProperty = MutableProperty(()) - public func optimizelyClientConfigurationFailed() { - self.optimizelyClientConfigurationFailedProperty.value = () + fileprivate let remoteConfigClientConfigurationFailedProperty = MutableProperty(()) + public func remoteConfigClientConfigurationFailed() { + self.remoteConfigClientConfigurationFailedProperty.value = () } fileprivate let pageTransitionCompletedProperty = MutableProperty(false) diff --git a/Library/ViewModels/DiscoveryViewModelTests.swift b/Library/ViewModels/DiscoveryViewModelTests.swift index fd54e1aaca..0bb53f8200 100644 --- a/Library/ViewModels/DiscoveryViewModelTests.swift +++ b/Library/ViewModels/DiscoveryViewModelTests.swift @@ -47,7 +47,7 @@ internal final class DiscoveryViewModelTests: TestCase { withEnvironment(optimizelyClient: MockOptimizelyClient()) { self.vm.inputs.viewDidLoad() self.vm.inputs.viewWillAppear(animated: false) - self.vm.inputs.optimizelyClientConfigured() + self.vm.inputs.remoteConfigClientConfigured() self.configureDataSource.assertValueCount(1) } @@ -58,7 +58,7 @@ internal final class DiscoveryViewModelTests: TestCase { self.vm.inputs.viewDidLoad() self.vm.inputs.viewWillAppear(animated: false) - self.vm.inputs.optimizelyClientConfigurationFailed() + self.vm.inputs.remoteConfigClientConfigurationFailed() self.configureDataSource.assertValueCount(1) } @@ -95,7 +95,7 @@ internal final class DiscoveryViewModelTests: TestCase { self.loadFilterIntoDataSource.assertDidNotEmitValue("Waits for Optimizely configuration") - self.vm.inputs.optimizelyClientConfigured() + self.vm.inputs.remoteConfigClientConfigured() self.scheduler.advance() @@ -119,7 +119,7 @@ internal final class DiscoveryViewModelTests: TestCase { self.loadFilterIntoDataSource.assertDidNotEmitValue("Waits for Optimizely configuration") - self.vm.inputs.optimizelyClientConfigurationFailed() + self.vm.inputs.remoteConfigClientConfigurationFailed() self.scheduler.advance() @@ -218,7 +218,7 @@ internal final class DiscoveryViewModelTests: TestCase { self.configureNavigationHeader.assertDidNotEmitValue("Waits for Optimizely configuration") - self.vm.inputs.optimizelyClientConfigured() + self.vm.inputs.remoteConfigClientConfigured() self.scheduler.advance() @@ -242,7 +242,7 @@ internal final class DiscoveryViewModelTests: TestCase { self.configureNavigationHeader.assertDidNotEmitValue("Waits for Optimizely configuration") - self.vm.inputs.optimizelyClientConfigurationFailed() + self.vm.inputs.remoteConfigClientConfigurationFailed() self.scheduler.advance()