From a6fc40d7f0a306a69a56983f79d0d3e9a1034b4e Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Wed, 24 Jan 2024 15:22:26 +0100 Subject: [PATCH 01/21] chore: added abstract layer for push notifications --- Core/Core.xcodeproj/project.pbxproj | 4 ++ .../Configuration/Config/BrazeConfig.swift | 32 +++++++++ Core/Core/Configuration/Config/Config.swift | 1 + .../CoreTests/Configuration/ConfigTests.swift | 10 +++ OpenEdX.xcodeproj/project.pbxproj | 64 ++++++++++++++++- OpenEdX/AppDelegate.swift | 14 ++++ OpenEdX/DI/AppAssembly.swift | 6 ++ .../AnalyticsManager}/AnalyticsManager.swift | 0 .../MainScreenAnalytics.swift | 0 .../Listeners/BrazeListener.swift | 14 ++++ .../Listeners/FCMListener.swift | 14 ++++ .../Providers/BrazeProvider.swift | 17 +++++ .../Providers/FCMProvider.swift | 17 +++++ .../PushNotificationsManager.swift | 68 +++++++++++++++++++ 14 files changed, 259 insertions(+), 2 deletions(-) create mode 100644 Core/Core/Configuration/Config/BrazeConfig.swift rename OpenEdX/{ => Managers/AnalyticsManager}/AnalyticsManager.swift (100%) rename OpenEdX/{ => Managers/AnalyticsManager}/MainScreenAnalytics.swift (100%) create mode 100644 OpenEdX/Managers/PushNotificationsManager/Listeners/BrazeListener.swift create mode 100644 OpenEdX/Managers/PushNotificationsManager/Listeners/FCMListener.swift create mode 100644 OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift create mode 100644 OpenEdX/Managers/PushNotificationsManager/Providers/FCMProvider.swift create mode 100644 OpenEdX/Managers/PushNotificationsManager/PushNotificationsManager.swift diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 8b9dae2f0..0f61e2d47 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -125,6 +125,7 @@ 07DDFCBD29A780BB00572595 /* UINavigationController+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07DDFCBC29A780BB00572595 /* UINavigationController+Animation.swift */; }; 07E0939F2B308D2800F1E4B2 /* Data_Certificate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07E0939E2B308D2800F1E4B2 /* Data_Certificate.swift */; }; A53A32352B233DEC005FE38A /* ThemeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A32342B233DEC005FE38A /* ThemeConfig.swift */; }; + A5F4E7B52B61544A00ACD166 /* BrazeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F4E7B42B61544A00ACD166 /* BrazeConfig.swift */; }; BA30427F2B20B320009B64B7 /* SocialAuthError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA30427D2B20B299009B64B7 /* SocialAuthError.swift */; }; BA593F1C2AF8E498009ADB51 /* ScrollSlidingTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA593F1B2AF8E498009ADB51 /* ScrollSlidingTabBar.swift */; }; BA593F1E2AF8E4A0009ADB51 /* FrameReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA593F1D2AF8E4A0009ADB51 /* FrameReader.swift */; }; @@ -294,6 +295,7 @@ 60153262DBC2F9E660D7E11B /* Pods-App-Core.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.release.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.release.xcconfig"; sourceTree = ""; }; 9D5B06CAA99EA5CD49CBE2BB /* Pods-App-Core.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.debugdev.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.debugdev.xcconfig"; sourceTree = ""; }; A53A32342B233DEC005FE38A /* ThemeConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeConfig.swift; sourceTree = ""; }; + A5F4E7B42B61544A00ACD166 /* BrazeConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrazeConfig.swift; sourceTree = ""; }; BA30427D2B20B299009B64B7 /* SocialAuthError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocialAuthError.swift; sourceTree = ""; }; BA593F1B2AF8E498009ADB51 /* ScrollSlidingTabBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollSlidingTabBar.swift; sourceTree = ""; }; BA593F1D2AF8E4A0009ADB51 /* FrameReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FrameReader.swift; sourceTree = ""; }; @@ -749,6 +751,7 @@ 0604C9A92B22FACF00AD5DBF /* UIComponentsConfig.swift */, 0727876F28D23411002E9142 /* Config.swift */, DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */, + A5F4E7B42B61544A00ACD166 /* BrazeConfig.swift */, DBF6F2492B0380E00098414B /* FeaturesConfig.swift */, DBF6F2452B01DAFE0098414B /* AgreementConfig.swift */, BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */, @@ -1054,6 +1057,7 @@ DBF6F2412B014ADA0098414B /* FirebaseConfig.swift in Sources */, 072787B628D37A0E002E9142 /* Validator.swift in Sources */, 0236961D28F9A2D200EEF206 /* Data_AuthResponse.swift in Sources */, + A5F4E7B52B61544A00ACD166 /* BrazeConfig.swift in Sources */, 02AFCC182AEFDB24000360F0 /* ThirdPartyMailClient.swift in Sources */, 0233D5712AF13EC800BAC8BD /* SelectMailClientView.swift in Sources */, BAFB99842B0E282E007D09F9 /* MicrosoftConfig.swift in Sources */, diff --git a/Core/Core/Configuration/Config/BrazeConfig.swift b/Core/Core/Configuration/Config/BrazeConfig.swift new file mode 100644 index 000000000..8410bf3f8 --- /dev/null +++ b/Core/Core/Configuration/Config/BrazeConfig.swift @@ -0,0 +1,32 @@ +// +// BrazeConfig.swift +// Core +// +// Created by Anton Yarmolenka on 24/01/2024. +// + +import Foundation + +private enum BrazeKeys: String { + case enabled = "ENABLED" + case pushNotificationsEnabled = "PUSH_NOTIFICATIONS_ENABLED" +} + +public final class BrazeConfig: NSObject { + public var enabled: Bool = false + public var pushNotificationsEnabled: Bool = false + + init(dictionary: [String: AnyObject]) { + super.init() + enabled = dictionary[BrazeKeys.enabled.rawValue] as? Bool == true + let pushNotificationsEnabled = dictionary[BrazeKeys.pushNotificationsEnabled.rawValue] as? Bool ?? false + self.pushNotificationsEnabled = enabled && pushNotificationsEnabled + } +} + +private let brazeKey = "BRAZE" +extension Config { + public var braze: BrazeConfig { + BrazeConfig(dictionary: self[brazeKey] as? [String: AnyObject] ?? [:]) + } +} diff --git a/Core/Core/Configuration/Config/Config.swift b/Core/Core/Configuration/Config/Config.swift index 7fcbe1c94..5c1693c97 100644 --- a/Core/Core/Configuration/Config/Config.swift +++ b/Core/Core/Configuration/Config/Config.swift @@ -25,6 +25,7 @@ public protocol ConfigProtocol { var theme: ThemeConfig { get } var uiComponents: UIComponentsConfig { get } var discovery: DiscoveryConfig { get } + var braze: BrazeConfig { get } } public enum TokenType: String { diff --git a/Core/CoreTests/Configuration/ConfigTests.swift b/Core/CoreTests/Configuration/ConfigTests.swift index 32638e484..e9b8a0ce7 100644 --- a/Core/CoreTests/Configuration/ConfigTests.swift +++ b/Core/CoreTests/Configuration/ConfigTests.swift @@ -52,6 +52,10 @@ class ConfigTests: XCTestCase { ], "APPLE_SIGNIN": [ "ENABLED": true + ], + "BRAZE": [ + "ENABLED": true, + "PUSH_NOTIFICATIONS_ENABLED": true ] ] @@ -115,4 +119,10 @@ class ConfigTests: XCTestCase { XCTAssertTrue(config.appleSignIn.enabled) } + + func testBrazeConfigInitialization() { + let config = Config(properties: properties) + + XCTAssertTrue(config.braze.pushNotificationsEnabled) + } } diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index 072473af5..b02ea1814 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -45,6 +45,11 @@ 07D5DA3E28D075AB00752FD9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 07D5DA3D28D075AB00752FD9 /* Assets.xcassets */; }; 07D5DA4128D075AB00752FD9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 07D5DA3F28D075AB00752FD9 /* LaunchScreen.storyboard */; }; 95C140F3BDF778364986E83B /* Pods_App_OpenEdX.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F138C15C3A2515F8F94DAA8B /* Pods_App_OpenEdX.framework */; }; + A500668B2B613ED10024680B /* PushNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A500668A2B613ED10024680B /* PushNotificationsManager.swift */; }; + A500668D2B6143000024680B /* FCMProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A500668C2B6143000024680B /* FCMProvider.swift */; }; + A50066912B61467B0024680B /* BrazeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50066902B61467B0024680B /* BrazeProvider.swift */; }; + A50066932B614DCD0024680B /* FCMListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50066922B614DCD0024680B /* FCMListener.swift */; }; + A50066952B614DEF0024680B /* BrazeListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50066942B614DEF0024680B /* BrazeListener.swift */; }; BA3042792B1F7147009B64B7 /* MSAL in Frameworks */ = {isa = PBXBuildFile; productRef = BA3042782B1F7147009B64B7 /* MSAL */; }; E0D6E6A32B1626B10089F9C9 /* Theme.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0D6E6A22B1626B10089F9C9 /* Theme.framework */; }; E0D6E6A42B1626D60089F9C9 /* Theme.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E0D6E6A22B1626B10089F9C9 /* Theme.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -115,6 +120,11 @@ 6F54C19C823A769E18923FA8 /* Pods-App-OpenEdX.debugstage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-OpenEdX.debugstage.xcconfig"; path = "Target Support Files/Pods-App-OpenEdX/Pods-App-OpenEdX.debugstage.xcconfig"; sourceTree = ""; }; 8284179FC05AEE2591573E20 /* Pods-App-OpenEdX.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-OpenEdX.debugdev.xcconfig"; path = "Target Support Files/Pods-App-OpenEdX/Pods-App-OpenEdX.debugdev.xcconfig"; sourceTree = ""; }; A24D6A8E1BC4DF46AD68904C /* Pods-App-OpenEdX.releaseprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-OpenEdX.releaseprod.xcconfig"; path = "Target Support Files/Pods-App-OpenEdX/Pods-App-OpenEdX.releaseprod.xcconfig"; sourceTree = ""; }; + A500668A2B613ED10024680B /* PushNotificationsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationsManager.swift; sourceTree = ""; }; + A500668C2B6143000024680B /* FCMProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCMProvider.swift; sourceTree = ""; }; + A50066902B61467B0024680B /* BrazeProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrazeProvider.swift; sourceTree = ""; }; + A50066922B614DCD0024680B /* FCMListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCMListener.swift; sourceTree = ""; }; + A50066942B614DEF0024680B /* BrazeListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrazeListener.swift; sourceTree = ""; }; A89AD827F52CF6A6B903606E /* Pods-App-OpenEdX.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-OpenEdX.releasestage.xcconfig"; path = "Target Support Files/Pods-App-OpenEdX/Pods-App-OpenEdX.releasestage.xcconfig"; sourceTree = ""; }; BB08ACD2CCA33D6DDDDD31B4 /* Pods-App-OpenEdX.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-OpenEdX.releasedev.xcconfig"; path = "Target Support Files/Pods-App-OpenEdX/Pods-App-OpenEdX.releasedev.xcconfig"; sourceTree = ""; }; E0D6E6A22B1626B10089F9C9 /* Theme.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Theme.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -200,8 +210,7 @@ 07D5DA3428D075AA00752FD9 /* AppDelegate.swift */, 0770DE1628D080A1006D8A5D /* RouteController.swift */, 0770DE1F28D0858A006D8A5D /* Router.swift */, - 0298DF2F2A4EF7230023A257 /* AnalyticsManager.swift */, - 02F175302A4DA95B0019CD70 /* MainScreenAnalytics.swift */, + A50066882B613E800024680B /* Managers */, 0293A2012A6FC9E30090A336 /* Data */, 0727878C28D347B2002E9142 /* View */, 0770DE1A28D084BC006D8A5D /* DI */, @@ -248,6 +257,52 @@ path = Pods; sourceTree = ""; }; + A50066872B613E4B0024680B /* PushNotificationsManager */ = { + isa = PBXGroup; + children = ( + A500668A2B613ED10024680B /* PushNotificationsManager.swift */, + A50066962B614F0C0024680B /* Providers */, + A50066972B614F2B0024680B /* Listeners */, + ); + path = PushNotificationsManager; + sourceTree = ""; + }; + A50066882B613E800024680B /* Managers */ = { + isa = PBXGroup; + children = ( + A50066872B613E4B0024680B /* PushNotificationsManager */, + A50066892B613E990024680B /* AnalyticsManager */, + ); + path = Managers; + sourceTree = ""; + }; + A50066892B613E990024680B /* AnalyticsManager */ = { + isa = PBXGroup; + children = ( + 0298DF2F2A4EF7230023A257 /* AnalyticsManager.swift */, + 02F175302A4DA95B0019CD70 /* MainScreenAnalytics.swift */, + ); + path = AnalyticsManager; + sourceTree = ""; + }; + A50066962B614F0C0024680B /* Providers */ = { + isa = PBXGroup; + children = ( + A500668C2B6143000024680B /* FCMProvider.swift */, + A50066902B61467B0024680B /* BrazeProvider.swift */, + ); + path = Providers; + sourceTree = ""; + }; + A50066972B614F2B0024680B /* Listeners */ = { + isa = PBXGroup; + children = ( + A50066922B614DCD0024680B /* FCMListener.swift */, + A50066942B614DEF0024680B /* BrazeListener.swift */, + ); + path = Listeners; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -413,9 +468,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A500668B2B613ED10024680B /* PushNotificationsManager.swift in Sources */, 0293A2052A6FCD430090A336 /* CoursePersistence.swift in Sources */, 020CA5D92AA0A25300970AAF /* AppStorage.swift in Sources */, 0298DF302A4EF7230023A257 /* AnalyticsManager.swift in Sources */, + A50066912B61467B0024680B /* BrazeProvider.swift in Sources */, 0293A2072A6FCDA30090A336 /* DiscoveryPersistence.swift in Sources */, 07D5DA3528D075AA00752FD9 /* AppDelegate.swift in Sources */, 02F175312A4DA95B0019CD70 /* MainScreenAnalytics.swift in Sources */, @@ -423,11 +480,14 @@ 0770DE5028D0A707006D8A5D /* NetworkAssembly.swift in Sources */, 0293A2032A6FCA590090A336 /* CorePersistence.swift in Sources */, 0770DE1E28D084E8006D8A5D /* AppAssembly.swift in Sources */, + A50066932B614DCD0024680B /* FCMListener.swift in Sources */, + A500668D2B6143000024680B /* FCMProvider.swift in Sources */, 025AD4AC2A6FB95C00AB8FA7 /* DatabaseManager.swift in Sources */, 024E69202AEFC3FB00FA0B59 /* MainScreenViewModel.swift in Sources */, 0770DE2028D0858A006D8A5D /* Router.swift in Sources */, 0293A2092A6FCDE50090A336 /* DashboardPersistence.swift in Sources */, 0770DE1728D080A1006D8A5D /* RouteController.swift in Sources */, + A50066952B614DEF0024680B /* BrazeListener.swift in Sources */, 071009C928D1DB3F00344290 /* ScreenAssembly.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/OpenEdX/AppDelegate.swift b/OpenEdX/AppDelegate.swift index 8b6b5bd1f..0a8a1c5b1 100644 --- a/OpenEdX/AppDelegate.swift +++ b/OpenEdX/AppDelegate.swift @@ -123,4 +123,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate { window?.rootViewController = RouteController() } + // Push Notifications + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + let pushManager = Container.shared.resolve(PushNotificationsManager.self)! + pushManager.didRegisterForRemoteNotificationsWithDeviceToken(deviceToken: deviceToken) + } + func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { + let pushManager = Container.shared.resolve(PushNotificationsManager.self)! + pushManager.didFailToRegisterForRemoteNotificationsWithError(error: error) + } + func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + let pushManager = Container.shared.resolve(PushNotificationsManager.self)! + pushManager.didReceiveRemoteNotification(userInfo: userInfo) + completionHandler(.newData) + } } diff --git a/OpenEdX/DI/AppAssembly.swift b/OpenEdX/DI/AppAssembly.swift index e5a491fc7..4e24e6924 100644 --- a/OpenEdX/DI/AppAssembly.swift +++ b/OpenEdX/DI/AppAssembly.swift @@ -157,6 +157,12 @@ class AppAssembly: Assembly { container.register(Validator.self) { _ in Validator() }.inObjectScope(.container) + + container.register(PushNotificationsManager.self) { r in + PushNotificationsManager( + config: r.resolve(ConfigProtocol.self)! + ) + }.inObjectScope(.container) } } // swiftlint:enable function_body_length diff --git a/OpenEdX/AnalyticsManager.swift b/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift similarity index 100% rename from OpenEdX/AnalyticsManager.swift rename to OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift diff --git a/OpenEdX/MainScreenAnalytics.swift b/OpenEdX/Managers/AnalyticsManager/MainScreenAnalytics.swift similarity index 100% rename from OpenEdX/MainScreenAnalytics.swift rename to OpenEdX/Managers/AnalyticsManager/MainScreenAnalytics.swift diff --git a/OpenEdX/Managers/PushNotificationsManager/Listeners/BrazeListener.swift b/OpenEdX/Managers/PushNotificationsManager/Listeners/BrazeListener.swift new file mode 100644 index 000000000..a705953e0 --- /dev/null +++ b/OpenEdX/Managers/PushNotificationsManager/Listeners/BrazeListener.swift @@ -0,0 +1,14 @@ +// +// BrazeListener.swift +// OpenEdX +// +// Created by Anton Yarmolenka on 24/01/2024. +// + +import Foundation + +class BrazeListener: PushNotificationsListener { + func didReceiveRemoteNotification(userInfo: [AnyHashable: Any]) { + + } +} diff --git a/OpenEdX/Managers/PushNotificationsManager/Listeners/FCMListener.swift b/OpenEdX/Managers/PushNotificationsManager/Listeners/FCMListener.swift new file mode 100644 index 000000000..3c0725e04 --- /dev/null +++ b/OpenEdX/Managers/PushNotificationsManager/Listeners/FCMListener.swift @@ -0,0 +1,14 @@ +// +// FCMListener.swift +// OpenEdX +// +// Created by Anton Yarmolenka on 24/01/2024. +// + +import Foundation + +class FCMListener: PushNotificationsListener { + func didReceiveRemoteNotification(userInfo: [AnyHashable: Any]) { + + } +} diff --git a/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift b/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift new file mode 100644 index 000000000..043e7d62b --- /dev/null +++ b/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift @@ -0,0 +1,17 @@ +// +// BrazeProvider.swift +// OpenEdX +// +// Created by Anton Yarmolenka on 24/01/2024. +// + +import Foundation + +class BrazeProvider: PushNotificationsProvider { + func didRegisterForRemoteNotificationsWithDeviceToken(deviceToken: Data) { + + } + func didFailToRegisterForRemoteNotificationsWithError(error: Error) { + + } +} diff --git a/OpenEdX/Managers/PushNotificationsManager/Providers/FCMProvider.swift b/OpenEdX/Managers/PushNotificationsManager/Providers/FCMProvider.swift new file mode 100644 index 000000000..739659e12 --- /dev/null +++ b/OpenEdX/Managers/PushNotificationsManager/Providers/FCMProvider.swift @@ -0,0 +1,17 @@ +// +// FCMProvider.swift +// OpenEdX +// +// Created by Anton Yarmolenka on 24/01/2024. +// + +import Foundation + +class FCMProvider: PushNotificationsProvider { + func didRegisterForRemoteNotificationsWithDeviceToken(deviceToken: Data) { + + } + func didFailToRegisterForRemoteNotificationsWithError(error: Error) { + + } +} diff --git a/OpenEdX/Managers/PushNotificationsManager/PushNotificationsManager.swift b/OpenEdX/Managers/PushNotificationsManager/PushNotificationsManager.swift new file mode 100644 index 000000000..23e747ccf --- /dev/null +++ b/OpenEdX/Managers/PushNotificationsManager/PushNotificationsManager.swift @@ -0,0 +1,68 @@ +// +// PushNotificationManager.swift +// OpenEdX +// +// Created by Anton Yarmolenka on 24/01/2024. +// + +import Foundation +import Core + +public protocol PushNotificationsProvider { + func didRegisterForRemoteNotificationsWithDeviceToken(deviceToken: Data) + func didFailToRegisterForRemoteNotificationsWithError(error: Error) +} + +protocol PushNotificationsListener { + func didReceiveRemoteNotification(userInfo: [AnyHashable: Any]) +} + +class PushNotificationsManager { + private var providers: [PushNotificationsProvider] = [] + private var listeners: [PushNotificationsListener] = [] + + // Init manager + public init(config: ConfigProtocol) { + self.providers = self.providersFor(config: config) + self.listeners = self.listenersFor(config: config) + } + + private func providersFor(config: ConfigProtocol) -> [PushNotificationsProvider] { + var rProviders: [PushNotificationsProvider] = [] + if config.firebase.cloudMessagingEnabled { + rProviders.append(FCMProvider()) + } + if config.braze.pushNotificationsEnabled { + rProviders.append(BrazeProvider()) + } + return rProviders + } + + private func listenersFor(config: ConfigProtocol) -> [PushNotificationsListener] { + var rListeners: [PushNotificationsListener] = [] + if config.firebase.cloudMessagingEnabled { + rListeners.append(FCMListener()) + } + if config.braze.pushNotificationsEnabled { + rListeners.append(BrazeListener()) + } + return rListeners + } + + // Proccess functions from app delegate + public func didRegisterForRemoteNotificationsWithDeviceToken(deviceToken: Data) { + for provider in providers { + provider.didRegisterForRemoteNotificationsWithDeviceToken(deviceToken: deviceToken) + } + } + public func didFailToRegisterForRemoteNotificationsWithError(error: Error) { + for provider in providers { + provider.didFailToRegisterForRemoteNotificationsWithError(error: error) + } + } + public func didReceiveRemoteNotification(userInfo: [AnyHashable: Any]) { + for listener in listeners { + listener.didReceiveRemoteNotification(userInfo: userInfo) + } + } +} From c87f7cb837eea0e9c00bbf2ceb86840c313decd2 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Fri, 26 Jan 2024 13:22:03 +0100 Subject: [PATCH 02/21] chore: abstract layer for deep linking --- Core/Core.xcodeproj/project.pbxproj | 4 ++ .../Configuration/Config/BranchConfig.swift | 31 ++++++++ Core/Core/Configuration/Config/Config.swift | 3 +- .../CoreTests/Configuration/ConfigTests.swift | 11 +++ OpenEdX.xcodeproj/project.pbxproj | 32 +++++++++ OpenEdX/AppDelegate.swift | 40 +++++++++-- OpenEdX/DI/AppAssembly.swift | 6 ++ .../DeepLinkManager/BranchService.swift | 35 +++++++++ .../DeepLinkManager/DeepLinkManager.swift | 72 +++++++++++++++++++ .../DeepLinkManager/Link/DeepLink.swift | 18 +++++ .../DeepLinkManager/Link/PushLink.swift | 30 ++++++++ .../Listeners/BrazeListener.swift | 4 +- .../Listeners/FCMListener.swift | 4 +- .../PushNotificationsManager.swift | 28 ++++++++ 14 files changed, 304 insertions(+), 14 deletions(-) create mode 100644 Core/Core/Configuration/Config/BranchConfig.swift create mode 100644 OpenEdX/Managers/DeepLinkManager/BranchService.swift create mode 100644 OpenEdX/Managers/DeepLinkManager/DeepLinkManager.swift create mode 100644 OpenEdX/Managers/DeepLinkManager/Link/DeepLink.swift create mode 100644 OpenEdX/Managers/DeepLinkManager/Link/PushLink.swift diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 0f61e2d47..4382f8035 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -125,6 +125,7 @@ 07DDFCBD29A780BB00572595 /* UINavigationController+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07DDFCBC29A780BB00572595 /* UINavigationController+Animation.swift */; }; 07E0939F2B308D2800F1E4B2 /* Data_Certificate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07E0939E2B308D2800F1E4B2 /* Data_Certificate.swift */; }; A53A32352B233DEC005FE38A /* ThemeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A32342B233DEC005FE38A /* ThemeConfig.swift */; }; + A595689B2B6173DF00ED4F90 /* BranchConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A595689A2B6173DF00ED4F90 /* BranchConfig.swift */; }; A5F4E7B52B61544A00ACD166 /* BrazeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F4E7B42B61544A00ACD166 /* BrazeConfig.swift */; }; BA30427F2B20B320009B64B7 /* SocialAuthError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA30427D2B20B299009B64B7 /* SocialAuthError.swift */; }; BA593F1C2AF8E498009ADB51 /* ScrollSlidingTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA593F1B2AF8E498009ADB51 /* ScrollSlidingTabBar.swift */; }; @@ -295,6 +296,7 @@ 60153262DBC2F9E660D7E11B /* Pods-App-Core.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.release.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.release.xcconfig"; sourceTree = ""; }; 9D5B06CAA99EA5CD49CBE2BB /* Pods-App-Core.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.debugdev.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.debugdev.xcconfig"; sourceTree = ""; }; A53A32342B233DEC005FE38A /* ThemeConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeConfig.swift; sourceTree = ""; }; + A595689A2B6173DF00ED4F90 /* BranchConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BranchConfig.swift; sourceTree = ""; }; A5F4E7B42B61544A00ACD166 /* BrazeConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrazeConfig.swift; sourceTree = ""; }; BA30427D2B20B299009B64B7 /* SocialAuthError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocialAuthError.swift; sourceTree = ""; }; BA593F1B2AF8E498009ADB51 /* ScrollSlidingTabBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollSlidingTabBar.swift; sourceTree = ""; }; @@ -752,6 +754,7 @@ 0727876F28D23411002E9142 /* Config.swift */, DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */, A5F4E7B42B61544A00ACD166 /* BrazeConfig.swift */, + A595689A2B6173DF00ED4F90 /* BranchConfig.swift */, DBF6F2492B0380E00098414B /* FeaturesConfig.swift */, DBF6F2452B01DAFE0098414B /* AgreementConfig.swift */, BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */, @@ -1030,6 +1033,7 @@ 021D925728DCF12900ACC565 /* AlertView.swift in Sources */, 027BD3A82909474200392132 /* KeyboardAvoidingViewController.swift in Sources */, 02E93F852AEBAEBC006C4750 /* AppReviewViewModel.swift in Sources */, + A595689B2B6173DF00ED4F90 /* BranchConfig.swift in Sources */, 0770DE2528D08FBA006D8A5D /* CoreStorage.swift in Sources */, BA8FA6612AD5974300EA029A /* AppleAuthProvider.swift in Sources */, BA8FA6702AD59EA300EA029A /* MicrosoftAuthProvider.swift in Sources */, diff --git a/Core/Core/Configuration/Config/BranchConfig.swift b/Core/Core/Configuration/Config/BranchConfig.swift new file mode 100644 index 000000000..33440a3a0 --- /dev/null +++ b/Core/Core/Configuration/Config/BranchConfig.swift @@ -0,0 +1,31 @@ +// +// BranchConfig.swift +// Core +// +// Created by Anton Yarmolenka on 24/01/2024. +// + +import Foundation + +private enum BranchKeys: String { + case enabled = "ENABLED" + case key = "KEY" +} + +public final class BranchConfig: NSObject { + public var enabled: Bool = false + public var key: String? + + init(dictionary: [String: AnyObject]) { + super.init() + enabled = dictionary[BranchKeys.enabled.rawValue] as? Bool == true + key = dictionary[BranchKeys.key.rawValue] as? String + } +} + +private let branchKey = "BRANCH" +extension Config { + public var branch: BranchConfig { + BranchConfig(dictionary: self[branchKey] as? [String: AnyObject] ?? [:]) + } +} diff --git a/Core/Core/Configuration/Config/Config.swift b/Core/Core/Configuration/Config/Config.swift index 5c1693c97..6aca3e6ca 100644 --- a/Core/Core/Configuration/Config/Config.swift +++ b/Core/Core/Configuration/Config/Config.swift @@ -26,6 +26,7 @@ public protocol ConfigProtocol { var uiComponents: UIComponentsConfig { get } var discovery: DiscoveryConfig { get } var braze: BrazeConfig { get } + var branch: BranchConfig { get } } public enum TokenType: String { @@ -65,7 +66,7 @@ public class Config { let dict = try? PropertyListSerialization.propertyList( from: data, options: [], - format: nil) as? [String: Any] + format: nil) as? [String: Any] else { return } properties = dict diff --git a/Core/CoreTests/Configuration/ConfigTests.swift b/Core/CoreTests/Configuration/ConfigTests.swift index e9b8a0ce7..21ab17ddf 100644 --- a/Core/CoreTests/Configuration/ConfigTests.swift +++ b/Core/CoreTests/Configuration/ConfigTests.swift @@ -56,6 +56,10 @@ class ConfigTests: XCTestCase { "BRAZE": [ "ENABLED": true, "PUSH_NOTIFICATIONS_ENABLED": true + ], + "BRANCH": [ + "ENABLED": true, + "KEY": "testBranchKey" ] ] @@ -125,4 +129,11 @@ class ConfigTests: XCTestCase { XCTAssertTrue(config.braze.pushNotificationsEnabled) } + + func testBranchConfigInitialization() { + let config = Config(properties: properties) + + XCTAssertTrue(config.branch.enabled) + XCTAssertEqual(config.branch.key, "testBranchKey") + } } diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index b02ea1814..c0c357d62 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -50,6 +50,10 @@ A50066912B61467B0024680B /* BrazeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50066902B61467B0024680B /* BrazeProvider.swift */; }; A50066932B614DCD0024680B /* FCMListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50066922B614DCD0024680B /* FCMListener.swift */; }; A50066952B614DEF0024680B /* BrazeListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50066942B614DEF0024680B /* BrazeListener.swift */; }; + A59568952B61630500ED4F90 /* DeepLinkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568942B61630500ED4F90 /* DeepLinkManager.swift */; }; + A59568972B61653700ED4F90 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568962B61653700ED4F90 /* DeepLink.swift */; }; + A59568992B616D9400ED4F90 /* PushLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568982B616D9400ED4F90 /* PushLink.swift */; }; + A59585AF2B62A07100A35A20 /* BranchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59585AE2B62A07100A35A20 /* BranchService.swift */; }; BA3042792B1F7147009B64B7 /* MSAL in Frameworks */ = {isa = PBXBuildFile; productRef = BA3042782B1F7147009B64B7 /* MSAL */; }; E0D6E6A32B1626B10089F9C9 /* Theme.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0D6E6A22B1626B10089F9C9 /* Theme.framework */; }; E0D6E6A42B1626D60089F9C9 /* Theme.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E0D6E6A22B1626B10089F9C9 /* Theme.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -125,6 +129,10 @@ A50066902B61467B0024680B /* BrazeProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrazeProvider.swift; sourceTree = ""; }; A50066922B614DCD0024680B /* FCMListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCMListener.swift; sourceTree = ""; }; A50066942B614DEF0024680B /* BrazeListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrazeListener.swift; sourceTree = ""; }; + A59568942B61630500ED4F90 /* DeepLinkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkManager.swift; sourceTree = ""; }; + A59568962B61653700ED4F90 /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = ""; }; + A59568982B616D9400ED4F90 /* PushLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushLink.swift; sourceTree = ""; }; + A59585AE2B62A07100A35A20 /* BranchService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BranchService.swift; sourceTree = ""; }; A89AD827F52CF6A6B903606E /* Pods-App-OpenEdX.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-OpenEdX.releasestage.xcconfig"; path = "Target Support Files/Pods-App-OpenEdX/Pods-App-OpenEdX.releasestage.xcconfig"; sourceTree = ""; }; BB08ACD2CCA33D6DDDDD31B4 /* Pods-App-OpenEdX.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-OpenEdX.releasedev.xcconfig"; path = "Target Support Files/Pods-App-OpenEdX/Pods-App-OpenEdX.releasedev.xcconfig"; sourceTree = ""; }; E0D6E6A22B1626B10089F9C9 /* Theme.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Theme.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -270,6 +278,7 @@ A50066882B613E800024680B /* Managers */ = { isa = PBXGroup; children = ( + A59568932B6162E400ED4F90 /* DeepLinkManager */, A50066872B613E4B0024680B /* PushNotificationsManager */, A50066892B613E990024680B /* AnalyticsManager */, ); @@ -303,6 +312,25 @@ path = Listeners; sourceTree = ""; }; + A59568932B6162E400ED4F90 /* DeepLinkManager */ = { + isa = PBXGroup; + children = ( + A59568942B61630500ED4F90 /* DeepLinkManager.swift */, + A59585AE2B62A07100A35A20 /* BranchService.swift */, + A59585AD2B62677B00A35A20 /* Link */, + ); + path = DeepLinkManager; + sourceTree = ""; + }; + A59585AD2B62677B00A35A20 /* Link */ = { + isa = PBXGroup; + children = ( + A59568962B61653700ED4F90 /* DeepLink.swift */, + A59568982B616D9400ED4F90 /* PushLink.swift */, + ); + path = Link; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -472,6 +500,7 @@ 0293A2052A6FCD430090A336 /* CoursePersistence.swift in Sources */, 020CA5D92AA0A25300970AAF /* AppStorage.swift in Sources */, 0298DF302A4EF7230023A257 /* AnalyticsManager.swift in Sources */, + A59585AF2B62A07100A35A20 /* BranchService.swift in Sources */, A50066912B61467B0024680B /* BrazeProvider.swift in Sources */, 0293A2072A6FCDA30090A336 /* DiscoveryPersistence.swift in Sources */, 07D5DA3528D075AA00752FD9 /* AppDelegate.swift in Sources */, @@ -487,8 +516,11 @@ 0770DE2028D0858A006D8A5D /* Router.swift in Sources */, 0293A2092A6FCDE50090A336 /* DashboardPersistence.swift in Sources */, 0770DE1728D080A1006D8A5D /* RouteController.swift in Sources */, + A59568952B61630500ED4F90 /* DeepLinkManager.swift in Sources */, A50066952B614DEF0024680B /* BrazeListener.swift in Sources */, 071009C928D1DB3F00344290 /* ScreenAssembly.swift in Sources */, + A59568972B61653700ED4F90 /* DeepLink.swift in Sources */, + A59568992B616D9400ED4F90 /* PushLink.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/OpenEdX/AppDelegate.swift b/OpenEdX/AppDelegate.swift index 0a8a1c5b1..2bc95b2ed 100644 --- a/OpenEdX/AppDelegate.swift +++ b/OpenEdX/AppDelegate.swift @@ -48,6 +48,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { didFinishLaunchingWithOptions: launchOptions ) } + configureDeepLinkServices(launchOptions: launchOptions) } Theme.Fonts.registerFonts() @@ -62,6 +63,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { object: nil ) + if let pushManager = Container.shared.resolve(PushNotificationsManager.self) { + pushManager.performRegistration() + } + return true } @@ -70,6 +75,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate { open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:] ) -> Bool { if let config = Container.shared.resolve(ConfigProtocol.self) { + if let deepLinkManager = Container.shared.resolve(DeepLinkManager.self), + deepLinkManager.serviceEnabled { + if deepLinkManager.handledURLWith(app: app, open: url, options: options) { + return true + } + } + if config.facebook.enabled { ApplicationDelegate.shared.application( app, @@ -125,16 +137,30 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Push Notifications func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { - let pushManager = Container.shared.resolve(PushNotificationsManager.self)! - pushManager.didRegisterForRemoteNotificationsWithDeviceToken(deviceToken: deviceToken) + if let pushManager = Container.shared.resolve(PushNotificationsManager.self) { + pushManager.didRegisterForRemoteNotificationsWithDeviceToken(deviceToken: deviceToken) + } } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { - let pushManager = Container.shared.resolve(PushNotificationsManager.self)! - pushManager.didFailToRegisterForRemoteNotificationsWithError(error: error) + if let pushManager = Container.shared.resolve(PushNotificationsManager.self) { + pushManager.didFailToRegisterForRemoteNotificationsWithError(error: error) + } } - func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { - let pushManager = Container.shared.resolve(PushNotificationsManager.self)! - pushManager.didReceiveRemoteNotification(userInfo: userInfo) + func application( + _ application: UIApplication, + didReceiveRemoteNotification userInfo: [AnyHashable: Any], + fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void + ) { + if let pushManager = Container.shared.resolve(PushNotificationsManager.self) { + pushManager.didReceiveRemoteNotification(userInfo: userInfo) + } completionHandler(.newData) } + + // Deep link + func configureDeepLinkServices(launchOptions: [UIApplication.LaunchOptionsKey: Any]?) { + if let deepLinkManager = Container.shared.resolve(DeepLinkManager.self) { + deepLinkManager.configureDeepLinkService(launchOptions: launchOptions) + } + } } diff --git a/OpenEdX/DI/AppAssembly.swift b/OpenEdX/DI/AppAssembly.swift index 4e24e6924..74101e5a2 100644 --- a/OpenEdX/DI/AppAssembly.swift +++ b/OpenEdX/DI/AppAssembly.swift @@ -163,6 +163,12 @@ class AppAssembly: Assembly { config: r.resolve(ConfigProtocol.self)! ) }.inObjectScope(.container) + + container.register(DeepLinkManager.self) { r in + DeepLinkManager( + config: r.resolve(ConfigProtocol.self)! + ) + }.inObjectScope(.container) } } // swiftlint:enable function_body_length diff --git a/OpenEdX/Managers/DeepLinkManager/BranchService.swift b/OpenEdX/Managers/DeepLinkManager/BranchService.swift new file mode 100644 index 000000000..4d36fb27d --- /dev/null +++ b/OpenEdX/Managers/DeepLinkManager/BranchService.swift @@ -0,0 +1,35 @@ +// +// BranchService.swift +// OpenEdX +// +// Created by Anton Yarmolenka on 25/01/2024. +// + +import Foundation +import UIKit + +class BranchService: DeepLinkService { + // configure service + func configureWith(launchOptions: [UIApplication.LaunchOptionsKey: Any]?) { + + } + + // handle url + func handledURLWith( + app: UIApplication, + open url: URL, + options: [UIApplication.OpenURLOptionsKey: Any] + ) -> Bool { + false + } + + // This method process push notification with the link object + func processNotification(with link: PushLink) { + + } + + // This method process the deep link with response parameters + func processDeepLink(with params: [String: Any]) { + + } +} diff --git a/OpenEdX/Managers/DeepLinkManager/DeepLinkManager.swift b/OpenEdX/Managers/DeepLinkManager/DeepLinkManager.swift new file mode 100644 index 000000000..ac945cec4 --- /dev/null +++ b/OpenEdX/Managers/DeepLinkManager/DeepLinkManager.swift @@ -0,0 +1,72 @@ +// +// DeepLinkManager.swift +// OpenEdX +// +// Created by Anton Yarmolenka on 24/01/2024. +// + +import Foundation +import Core +import UIKit + +public protocol DeepLinkService { + func processNotification(with link: PushLink) + func processDeepLink(with params: [String: Any]) + func configureWith(launchOptions: [UIApplication.LaunchOptionsKey: Any]?) + func handledURLWith(app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool +} + +class DeepLinkManager { + private var service: DeepLinkService? + + // Init manager + public init(config: ConfigProtocol) { + self.service = self.serviceFor(config: config) + } + + private func serviceFor(config: ConfigProtocol) -> DeepLinkService? { + if config.branch.enabled { + return BranchService() + } + return nil + } + + // check if service is added (means enabled) + var serviceEnabled: Bool { + service != nil + } + + // Configure services + func configureDeepLinkService(launchOptions: [UIApplication.LaunchOptionsKey: Any]?) { + if let service = service { + service.configureWith(launchOptions: launchOptions) + } + } + + // Handle open url + func handledURLWith( + app: UIApplication, + open url: URL, + options: [UIApplication.OpenURLOptionsKey: Any] = [:] + ) -> Bool { + if let service = service { + return service.handledURLWith(app: app, open: url, options: options) + } + return false + } + + // This method process push notification with the link object + func processNotification(with link: PushLink) { + if let service = service { + service.processNotification(with: link) + } + } + + // This method process the deep link with response parameters + func processDeepLink(with params: [String: Any]) { + if let service = service { + service.processDeepLink(with: params) + } + } + +} diff --git a/OpenEdX/Managers/DeepLinkManager/Link/DeepLink.swift b/OpenEdX/Managers/DeepLinkManager/Link/DeepLink.swift new file mode 100644 index 000000000..3df8a21e6 --- /dev/null +++ b/OpenEdX/Managers/DeepLinkManager/Link/DeepLink.swift @@ -0,0 +1,18 @@ +// +// DeepLink.swift +// OpenEdX +// +// Created by Anton Yarmolenka on 24/01/2024. +// + +import Foundation + +enum DeepLinkType: String { + case none +} + +public class DeepLink { + init(dictionary: [String: Any]) { + + } +} diff --git a/OpenEdX/Managers/DeepLinkManager/Link/PushLink.swift b/OpenEdX/Managers/DeepLinkManager/Link/PushLink.swift new file mode 100644 index 000000000..262bda304 --- /dev/null +++ b/OpenEdX/Managers/DeepLinkManager/Link/PushLink.swift @@ -0,0 +1,30 @@ +// +// PushLink.swift +// OpenEdX +// +// Created by Anton Yarmolenka on 24/01/2024. +// + +import Foundation + +enum DataKeys: String { + case title + case body + case aps + case alert +} + +// This link will have information of course and screen type which will be use to route on particular screen. +public class PushLink: DeepLink { + let title: String? + let body: String? + + override init(dictionary: [String: Any]) { + let aps = dictionary[DataKeys.aps.rawValue] as? [String: Any] + let alert = aps?[DataKeys.alert.rawValue] as? [String: Any] + title = alert?[DataKeys.title.rawValue] as? String + body = alert?[DataKeys.body.rawValue] as? String + + super.init(dictionary: dictionary) + } +} diff --git a/OpenEdX/Managers/PushNotificationsManager/Listeners/BrazeListener.swift b/OpenEdX/Managers/PushNotificationsManager/Listeners/BrazeListener.swift index a705953e0..7da4ddc4d 100644 --- a/OpenEdX/Managers/PushNotificationsManager/Listeners/BrazeListener.swift +++ b/OpenEdX/Managers/PushNotificationsManager/Listeners/BrazeListener.swift @@ -8,7 +8,5 @@ import Foundation class BrazeListener: PushNotificationsListener { - func didReceiveRemoteNotification(userInfo: [AnyHashable: Any]) { - - } + func notificationToThisListener(userinfo: [AnyHashable: Any]) -> Bool { false } } diff --git a/OpenEdX/Managers/PushNotificationsManager/Listeners/FCMListener.swift b/OpenEdX/Managers/PushNotificationsManager/Listeners/FCMListener.swift index 3c0725e04..ce965be51 100644 --- a/OpenEdX/Managers/PushNotificationsManager/Listeners/FCMListener.swift +++ b/OpenEdX/Managers/PushNotificationsManager/Listeners/FCMListener.swift @@ -8,7 +8,5 @@ import Foundation class FCMListener: PushNotificationsListener { - func didReceiveRemoteNotification(userInfo: [AnyHashable: Any]) { - - } + func notificationToThisListener(userinfo: [AnyHashable: Any]) -> Bool { false } } diff --git a/OpenEdX/Managers/PushNotificationsManager/PushNotificationsManager.swift b/OpenEdX/Managers/PushNotificationsManager/PushNotificationsManager.swift index 23e747ccf..4f6338e14 100644 --- a/OpenEdX/Managers/PushNotificationsManager/PushNotificationsManager.swift +++ b/OpenEdX/Managers/PushNotificationsManager/PushNotificationsManager.swift @@ -7,6 +7,8 @@ import Foundation import Core +import UIKit +import Swinject public protocol PushNotificationsProvider { func didRegisterForRemoteNotificationsWithDeviceToken(deviceToken: Data) @@ -14,9 +16,20 @@ public protocol PushNotificationsProvider { } protocol PushNotificationsListener { + func notificationToThisListener(userinfo: [AnyHashable: Any]) -> Bool func didReceiveRemoteNotification(userInfo: [AnyHashable: Any]) } +extension PushNotificationsListener { + func didReceiveRemoteNotification(userInfo: [AnyHashable: Any]) { + guard let dictionary = userInfo as? [String: Any], notificationToThisListener(userinfo: userInfo) else { return } + let link = PushLink(dictionary: dictionary) + if let deepLinkManager = Container.shared.resolve(DeepLinkManager.self) { + deepLinkManager.processNotification(with: link) + } + } +} + class PushNotificationsManager { private var providers: [PushNotificationsProvider] = [] private var listeners: [PushNotificationsListener] = [] @@ -49,6 +62,21 @@ class PushNotificationsManager { return rListeners } + // Register for push notifications + public func performRegistration() { + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (granted, error) in + if granted { + DispatchQueue.main.async { + UIApplication.shared.registerForRemoteNotifications() + } + } else if let error = error { + debugLog("Push notifications permission error: \(error.localizedDescription)") + } else { + debugLog("Permission for push notifications denied.") + } + } + } + // Proccess functions from app delegate public func didRegisterForRemoteNotificationsWithDeviceToken(deviceToken: Data) { for provider in providers { From 3f4049c2a02a507ada69279c2ee6ede74b58dbec Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Mon, 29 Jan 2024 15:47:24 +0100 Subject: [PATCH 03/21] chore: test braze experiment chore: test braze --- OpenEdX.xcodeproj/project.pbxproj | 33 +++++++++++++++++++ OpenEdX/AppDelegate.swift | 3 ++ .../DeepLinkManager/DeepLinkManager.swift | 2 +- .../Listeners/BrazeListener.swift | 7 +++- .../Providers/BrazeProvider.swift | 11 +++++-- Podfile.lock | 2 +- default_config/config_settings.yaml | 2 +- 7 files changed, 54 insertions(+), 6 deletions(-) diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index c0c357d62..2d122ada1 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -54,6 +54,9 @@ A59568972B61653700ED4F90 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568962B61653700ED4F90 /* DeepLink.swift */; }; A59568992B616D9400ED4F90 /* PushLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568982B616D9400ED4F90 /* PushLink.swift */; }; A59585AF2B62A07100A35A20 /* BranchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59585AE2B62A07100A35A20 /* BranchService.swift */; }; + A5BECBD02B67C161001776CD /* BrazeKit in Frameworks */ = {isa = PBXBuildFile; productRef = A5BECBCF2B67C161001776CD /* BrazeKit */; }; + A5BECBD22B67C161001776CD /* BrazeKitCompat in Frameworks */ = {isa = PBXBuildFile; productRef = A5BECBD12B67C161001776CD /* BrazeKitCompat */; }; + A5BECBD42B67C161001776CD /* BrazeLocation in Frameworks */ = {isa = PBXBuildFile; productRef = A5BECBD32B67C161001776CD /* BrazeLocation */; }; BA3042792B1F7147009B64B7 /* MSAL in Frameworks */ = {isa = PBXBuildFile; productRef = BA3042782B1F7147009B64B7 /* MSAL */; }; E0D6E6A32B1626B10089F9C9 /* Theme.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0D6E6A22B1626B10089F9C9 /* Theme.framework */; }; E0D6E6A42B1626D60089F9C9 /* Theme.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E0D6E6A22B1626B10089F9C9 /* Theme.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -152,7 +155,10 @@ BA3042792B1F7147009B64B7 /* MSAL in Frameworks */, 072787B128D34D83002E9142 /* Discovery.framework in Frameworks */, 0218196428F734FA00202564 /* Discussion.framework in Frameworks */, + A5BECBD02B67C161001776CD /* BrazeKit in Frameworks */, 0219C67728F4347600D64452 /* Course.framework in Frameworks */, + A5BECBD42B67C161001776CD /* BrazeLocation in Frameworks */, + A5BECBD22B67C161001776CD /* BrazeKitCompat in Frameworks */, 027DB33028D8A063002B6862 /* Dashboard.framework in Frameworks */, 95C140F3BDF778364986E83B /* Pods_App_OpenEdX.framework in Frameworks */, ); @@ -354,6 +360,9 @@ name = OpenEdX; packageProductDependencies = ( BA3042782B1F7147009B64B7 /* MSAL */, + A5BECBCF2B67C161001776CD /* BrazeKit */, + A5BECBD12B67C161001776CD /* BrazeKitCompat */, + A5BECBD32B67C161001776CD /* BrazeLocation */, ); productName = OpenEdX; productReference = 07D5DA3128D075AA00752FD9 /* OpenEdX.app */; @@ -386,6 +395,7 @@ mainGroup = 07D5DA2828D075AA00752FD9; packageReferences = ( BA3042772B1F7147009B64B7 /* XCRemoteSwiftPackageReference "microsoft-authentication-library-for-objc" */, + A5BECBCE2B67C161001776CD /* XCRemoteSwiftPackageReference "braze-swift-sdk" */, ); productRefGroup = 07D5DA3228D075AA00752FD9 /* Products */; projectDirPath = ""; @@ -1143,6 +1153,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + A5BECBCE2B67C161001776CD /* XCRemoteSwiftPackageReference "braze-swift-sdk" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/braze-inc/braze-swift-sdk"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 7.5.0; + }; + }; BA3042772B1F7147009B64B7 /* XCRemoteSwiftPackageReference "microsoft-authentication-library-for-objc" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/AzureAD/microsoft-authentication-library-for-objc"; @@ -1154,6 +1172,21 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + A5BECBCF2B67C161001776CD /* BrazeKit */ = { + isa = XCSwiftPackageProductDependency; + package = A5BECBCE2B67C161001776CD /* XCRemoteSwiftPackageReference "braze-swift-sdk" */; + productName = BrazeKit; + }; + A5BECBD12B67C161001776CD /* BrazeKitCompat */ = { + isa = XCSwiftPackageProductDependency; + package = A5BECBCE2B67C161001776CD /* XCRemoteSwiftPackageReference "braze-swift-sdk" */; + productName = BrazeKitCompat; + }; + A5BECBD32B67C161001776CD /* BrazeLocation */ = { + isa = XCSwiftPackageProductDependency; + package = A5BECBCE2B67C161001776CD /* XCRemoteSwiftPackageReference "braze-swift-sdk" */; + productName = BrazeLocation; + }; BA3042782B1F7147009B64B7 /* MSAL */ = { isa = XCSwiftPackageProductDependency; package = BA3042772B1F7147009B64B7 /* XCRemoteSwiftPackageReference "microsoft-authentication-library-for-objc" */; diff --git a/OpenEdX/AppDelegate.swift b/OpenEdX/AppDelegate.swift index 2bc95b2ed..567dd5899 100644 --- a/OpenEdX/AppDelegate.swift +++ b/OpenEdX/AppDelegate.swift @@ -16,6 +16,7 @@ import GoogleSignIn import FacebookCore import MSAL import Theme +import BrazeKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -24,6 +25,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { UIApplication.shared.delegate as! AppDelegate } + var braze: Braze? + var window: UIWindow? private var assembler: Assembler? diff --git a/OpenEdX/Managers/DeepLinkManager/DeepLinkManager.swift b/OpenEdX/Managers/DeepLinkManager/DeepLinkManager.swift index ac945cec4..20bd8f18f 100644 --- a/OpenEdX/Managers/DeepLinkManager/DeepLinkManager.swift +++ b/OpenEdX/Managers/DeepLinkManager/DeepLinkManager.swift @@ -46,7 +46,7 @@ class DeepLinkManager { // Handle open url func handledURLWith( app: UIApplication, - open url: URL, + open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:] ) -> Bool { if let service = service { diff --git a/OpenEdX/Managers/PushNotificationsManager/Listeners/BrazeListener.swift b/OpenEdX/Managers/PushNotificationsManager/Listeners/BrazeListener.swift index 7da4ddc4d..f9b34fa60 100644 --- a/OpenEdX/Managers/PushNotificationsManager/Listeners/BrazeListener.swift +++ b/OpenEdX/Managers/PushNotificationsManager/Listeners/BrazeListener.swift @@ -8,5 +8,10 @@ import Foundation class BrazeListener: PushNotificationsListener { - func notificationToThisListener(userinfo: [AnyHashable: Any]) -> Bool { false } + func notificationToThisListener(userinfo: [AnyHashable: Any]) -> Bool { + //A push notification sent from the braze has a key ab in it like ab = {c = "c_value";}; + guard let _ = userinfo["ab"] as? [String : Any], userinfo.count > 0 + else { return false } + return true + } } diff --git a/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift b/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift index 043e7d62b..01d4d9f99 100644 --- a/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift +++ b/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift @@ -6,12 +6,19 @@ // import Foundation +import BrazeKit +import UIKit class BrazeProvider: PushNotificationsProvider { func didRegisterForRemoteNotificationsWithDeviceToken(deviceToken: Data) { - + let configuration = Braze.Configuration( + apiKey: "", + endpoint: "" + ) + let braze = Braze(configuration: configuration) + braze.notifications.register(deviceToken: deviceToken) + (UIApplication.shared.delegate as? AppDelegate)?.braze = braze } func didFailToRegisterForRemoteNotificationsWithError(error: Error) { - } } diff --git a/Podfile.lock b/Podfile.lock index d9169ef4b..fadc65f3b 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -182,4 +182,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 544edab2f9ecc4ac18973fb8865f1d0613ec8a28 -COCOAPODS: 1.13.0 +COCOAPODS: 1.14.2 diff --git a/default_config/config_settings.yaml b/default_config/config_settings.yaml index 249e93fc3..c63b34959 100644 --- a/default_config/config_settings.yaml +++ b/default_config/config_settings.yaml @@ -1,4 +1,4 @@ -config_directory: './default_config' +config_directory: './../edx-mobile-config' config_mapping: prod: 'prod' stage: 'stage' From 8bc62e79541479953f5a4083e3a6ba1b627514cb Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Fri, 2 Feb 2024 15:45:56 +0100 Subject: [PATCH 04/21] chore: added segment package --- .../Presentation/AuthorizationAnalytics.swift | 4 +- .../Presentation/Login/SignInViewModel.swift | 4 +- .../Registration/SignUpViewModel.swift | 4 +- Core/Core.xcodeproj/project.pbxproj | 4 + Core/Core/Configuration/Config/Config.swift | 1 + .../Configuration/Config/SegmentConfig.swift | 31 ++++++++ OpenEdX.xcodeproj/project.pbxproj | 70 ++++++++++++------ OpenEdX/AppDelegate.swift | 18 ++++- .../AnalyticsManager/AnalyticsManager.swift | 16 +++- .../Providers/BrazeProvider.swift | 18 +++-- OpenEdX/RouteController.swift | 2 +- Podfile | 2 +- Podfile.lock | 73 +++---------------- 13 files changed, 139 insertions(+), 108 deletions(-) create mode 100644 Core/Core/Configuration/Config/SegmentConfig.swift diff --git a/Authorization/Authorization/Presentation/AuthorizationAnalytics.swift b/Authorization/Authorization/Presentation/AuthorizationAnalytics.swift index d73799435..060560ba6 100644 --- a/Authorization/Authorization/Presentation/AuthorizationAnalytics.swift +++ b/Authorization/Authorization/Presentation/AuthorizationAnalytics.swift @@ -30,7 +30,7 @@ public enum SocialAuthMethod: String { //sourcery: AutoMockable public protocol AuthorizationAnalytics { - func setUserID(_ id: String) + func identify(id: String, username: String, email: String) func userLogin(method: AuthMethod) func signUpClicked() func createAccountClicked() @@ -41,7 +41,7 @@ public protocol AuthorizationAnalytics { #if DEBUG class AuthorizationAnalyticsMock: AuthorizationAnalytics { - public func setUserID(_ id: String) {} + func identify(id: String, username: String, email: String) {} public func userLogin(method: AuthMethod) {} public func signUpClicked() {} public func createAccountClicked() {} diff --git a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift index 3edd28bcf..deebec363 100644 --- a/Authorization/Authorization/Presentation/Login/SignInViewModel.swift +++ b/Authorization/Authorization/Presentation/Login/SignInViewModel.swift @@ -79,7 +79,7 @@ public class SignInViewModel: ObservableObject { isShowProgress = true do { let user = try await interactor.login(username: username, password: password) - analytics.setUserID("\(user.id)") + analytics.identify(id: "\(user.id)", username: user.username, email: user.email) analytics.userLogin(method: .password) router.showMainOrWhatsNewScreen(sourceScreen: sourceScreen) } catch let error { @@ -110,7 +110,7 @@ public class SignInViewModel: ObservableObject { isShowProgress = true do { let user = try await interactor.login(externalToken: externalToken, backend: backend) - analytics.setUserID("\(user.id)") + analytics.identify(id: "\(user.id)", username: user.username, email: user.email) analytics.userLogin(method: authMethod) router.showMainOrWhatsNewScreen(sourceScreen: sourceScreen) } catch let error { diff --git a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift index 760f836dd..ff5d30452 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpViewModel.swift @@ -115,7 +115,7 @@ public class SignUpViewModel: ObservableObject { fields: validateFields, isSocial: externalToken != nil ) - analytics.setUserID("\(user.id)") + analytics.identify(id: "\(user.id)", username: user.username, email: user.email) analytics.registrationSuccess() isShowProgress = false router.showMainOrWhatsNewScreen(sourceScreen: sourceScreen) @@ -172,7 +172,7 @@ public class SignUpViewModel: ObservableObject { do { isShowProgress = true let user = try await interactor.login(externalToken: response.token, backend: backend) - analytics.setUserID("\(user.id)") + analytics.identify(id: "\(user.id)", username: user.username, email: user.email) analytics.userLogin(method: authMethod) isShowProgress = false router.showMainOrWhatsNewScreen(sourceScreen: sourceScreen) diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 4382f8035..1149e0489 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -124,6 +124,7 @@ 0770DE6128D0B2CB006D8A5D /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE6028D0B2CB006D8A5D /* Assets.swift */; }; 07DDFCBD29A780BB00572595 /* UINavigationController+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07DDFCBC29A780BB00572595 /* UINavigationController+Animation.swift */; }; 07E0939F2B308D2800F1E4B2 /* Data_Certificate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07E0939E2B308D2800F1E4B2 /* Data_Certificate.swift */; }; + A51CDBE72B6D21F2009B6D4E /* SegmentConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51CDBE62B6D21F2009B6D4E /* SegmentConfig.swift */; }; A53A32352B233DEC005FE38A /* ThemeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A32342B233DEC005FE38A /* ThemeConfig.swift */; }; A595689B2B6173DF00ED4F90 /* BranchConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A595689A2B6173DF00ED4F90 /* BranchConfig.swift */; }; A5F4E7B52B61544A00ACD166 /* BrazeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F4E7B42B61544A00ACD166 /* BrazeConfig.swift */; }; @@ -295,6 +296,7 @@ 3B74C6685E416657F3C5F5A8 /* Pods-App-Core.releaseprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.releaseprod.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.releaseprod.xcconfig"; sourceTree = ""; }; 60153262DBC2F9E660D7E11B /* Pods-App-Core.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.release.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.release.xcconfig"; sourceTree = ""; }; 9D5B06CAA99EA5CD49CBE2BB /* Pods-App-Core.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.debugdev.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.debugdev.xcconfig"; sourceTree = ""; }; + A51CDBE62B6D21F2009B6D4E /* SegmentConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentConfig.swift; sourceTree = ""; }; A53A32342B233DEC005FE38A /* ThemeConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeConfig.swift; sourceTree = ""; }; A595689A2B6173DF00ED4F90 /* BranchConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BranchConfig.swift; sourceTree = ""; }; A5F4E7B42B61544A00ACD166 /* BrazeConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrazeConfig.swift; sourceTree = ""; }; @@ -755,6 +757,7 @@ DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */, A5F4E7B42B61544A00ACD166 /* BrazeConfig.swift */, A595689A2B6173DF00ED4F90 /* BranchConfig.swift */, + A51CDBE62B6D21F2009B6D4E /* SegmentConfig.swift */, DBF6F2492B0380E00098414B /* FeaturesConfig.swift */, DBF6F2452B01DAFE0098414B /* AgreementConfig.swift */, BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */, @@ -995,6 +998,7 @@ 0236961F28F9A2F600EEF206 /* AuthEndpoint.swift in Sources */, 02B3E3B32930198600A50475 /* AVPlayerViewControllerExtension.swift in Sources */, 0295C885299B99DD00ABE571 /* RefreshableScrollView.swift in Sources */, + A51CDBE72B6D21F2009B6D4E /* SegmentConfig.swift in Sources */, 0282DA7328F98CC9003C3F07 /* WebUnitView.swift in Sources */, E0D586362B314CD3009B4BA7 /* LogistrationBottomView.swift in Sources */, 0727878128D25EFD002E9142 /* SnackBarView.swift in Sources */, diff --git a/Core/Core/Configuration/Config/Config.swift b/Core/Core/Configuration/Config/Config.swift index 6aca3e6ca..ae60e26eb 100644 --- a/Core/Core/Configuration/Config/Config.swift +++ b/Core/Core/Configuration/Config/Config.swift @@ -27,6 +27,7 @@ public protocol ConfigProtocol { var discovery: DiscoveryConfig { get } var braze: BrazeConfig { get } var branch: BranchConfig { get } + var segment: SegmentConfig { get } } public enum TokenType: String { diff --git a/Core/Core/Configuration/Config/SegmentConfig.swift b/Core/Core/Configuration/Config/SegmentConfig.swift new file mode 100644 index 000000000..37ed53cc9 --- /dev/null +++ b/Core/Core/Configuration/Config/SegmentConfig.swift @@ -0,0 +1,31 @@ +// +// SegmentConfig.swift +// Core +// +// Created by Anton Yarmolenka on 02/02/2024. +// + +import Foundation + +private enum SegmentKeys: String, RawStringExtractable { + case enabled = "ENABLED" + case writeKey = "SEGMENT_IO_WRITE_KEY" +} + +public final class SegmentConfig: NSObject { + public var enabled: Bool = false + public var writeKey: String = "" + + init(dictionary: [String: AnyObject]) { + super.init() + enabled = dictionary[SegmentKeys.enabled] as? Bool == true + writeKey = dictionary[SegmentKeys.writeKey] as? String ?? "" + } +} + +private let segmentKey = "SEGMENT_IO" +extension Config { + public var segment: SegmentConfig { + SegmentConfig(dictionary: self[segmentKey] as? [String: AnyObject] ?? [:]) + } +} diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index 2d122ada1..13265bdee 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -50,13 +50,14 @@ A50066912B61467B0024680B /* BrazeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50066902B61467B0024680B /* BrazeProvider.swift */; }; A50066932B614DCD0024680B /* FCMListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50066922B614DCD0024680B /* FCMListener.swift */; }; A50066952B614DEF0024680B /* BrazeListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50066942B614DEF0024680B /* BrazeListener.swift */; }; + A51CDBE52B6D1E93009B6D4E /* Segment in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBE42B6D1E93009B6D4E /* Segment */; }; + A51CDBEA2B6D25C9009B6D4E /* SegmentFirebase in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBE92B6D25C9009B6D4E /* SegmentFirebase */; }; + A51CDBED2B6D2BEE009B6D4E /* SegmentBraze in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBEC2B6D2BEE009B6D4E /* SegmentBraze */; }; + A51CDBEF2B6D2BEE009B6D4E /* SegmentBrazeUI in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBEE2B6D2BEE009B6D4E /* SegmentBrazeUI */; }; A59568952B61630500ED4F90 /* DeepLinkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568942B61630500ED4F90 /* DeepLinkManager.swift */; }; A59568972B61653700ED4F90 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568962B61653700ED4F90 /* DeepLink.swift */; }; A59568992B616D9400ED4F90 /* PushLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568982B616D9400ED4F90 /* PushLink.swift */; }; A59585AF2B62A07100A35A20 /* BranchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59585AE2B62A07100A35A20 /* BranchService.swift */; }; - A5BECBD02B67C161001776CD /* BrazeKit in Frameworks */ = {isa = PBXBuildFile; productRef = A5BECBCF2B67C161001776CD /* BrazeKit */; }; - A5BECBD22B67C161001776CD /* BrazeKitCompat in Frameworks */ = {isa = PBXBuildFile; productRef = A5BECBD12B67C161001776CD /* BrazeKitCompat */; }; - A5BECBD42B67C161001776CD /* BrazeLocation in Frameworks */ = {isa = PBXBuildFile; productRef = A5BECBD32B67C161001776CD /* BrazeLocation */; }; BA3042792B1F7147009B64B7 /* MSAL in Frameworks */ = {isa = PBXBuildFile; productRef = BA3042782B1F7147009B64B7 /* MSAL */; }; E0D6E6A32B1626B10089F9C9 /* Theme.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0D6E6A22B1626B10089F9C9 /* Theme.framework */; }; E0D6E6A42B1626D60089F9C9 /* Theme.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E0D6E6A22B1626B10089F9C9 /* Theme.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -153,14 +154,15 @@ 025DE1A428DB4DAE0053E0F4 /* Profile.framework in Frameworks */, 0770DE4B28D0A462006D8A5D /* Authorization.framework in Frameworks */, BA3042792B1F7147009B64B7 /* MSAL in Frameworks */, + A51CDBE52B6D1E93009B6D4E /* Segment in Frameworks */, 072787B128D34D83002E9142 /* Discovery.framework in Frameworks */, 0218196428F734FA00202564 /* Discussion.framework in Frameworks */, - A5BECBD02B67C161001776CD /* BrazeKit in Frameworks */, + A51CDBEA2B6D25C9009B6D4E /* SegmentFirebase in Frameworks */, 0219C67728F4347600D64452 /* Course.framework in Frameworks */, - A5BECBD42B67C161001776CD /* BrazeLocation in Frameworks */, - A5BECBD22B67C161001776CD /* BrazeKitCompat in Frameworks */, 027DB33028D8A063002B6862 /* Dashboard.framework in Frameworks */, 95C140F3BDF778364986E83B /* Pods_App_OpenEdX.framework in Frameworks */, + A51CDBED2B6D2BEE009B6D4E /* SegmentBraze in Frameworks */, + A51CDBEF2B6D2BEE009B6D4E /* SegmentBrazeUI in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -360,9 +362,10 @@ name = OpenEdX; packageProductDependencies = ( BA3042782B1F7147009B64B7 /* MSAL */, - A5BECBCF2B67C161001776CD /* BrazeKit */, - A5BECBD12B67C161001776CD /* BrazeKitCompat */, - A5BECBD32B67C161001776CD /* BrazeLocation */, + A51CDBE42B6D1E93009B6D4E /* Segment */, + A51CDBE92B6D25C9009B6D4E /* SegmentFirebase */, + A51CDBEC2B6D2BEE009B6D4E /* SegmentBraze */, + A51CDBEE2B6D2BEE009B6D4E /* SegmentBrazeUI */, ); productName = OpenEdX; productReference = 07D5DA3128D075AA00752FD9 /* OpenEdX.app */; @@ -395,7 +398,9 @@ mainGroup = 07D5DA2828D075AA00752FD9; packageReferences = ( BA3042772B1F7147009B64B7 /* XCRemoteSwiftPackageReference "microsoft-authentication-library-for-objc" */, - A5BECBCE2B67C161001776CD /* XCRemoteSwiftPackageReference "braze-swift-sdk" */, + A51CDBE32B6D1E93009B6D4E /* XCRemoteSwiftPackageReference "analytics-swift" */, + A51CDBE82B6D25C9009B6D4E /* XCRemoteSwiftPackageReference "analytics-swift-firebase" */, + A51CDBEB2B6D2BEE009B6D4E /* XCRemoteSwiftPackageReference "braze-segment-swift" */, ); productRefGroup = 07D5DA3228D075AA00752FD9 /* Products */; projectDirPath = ""; @@ -1153,12 +1158,28 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - A5BECBCE2B67C161001776CD /* XCRemoteSwiftPackageReference "braze-swift-sdk" */ = { + A51CDBE32B6D1E93009B6D4E /* XCRemoteSwiftPackageReference "analytics-swift" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/braze-inc/braze-swift-sdk"; + repositoryURL = "git@github.com:segmentio/analytics-swift.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 7.5.0; + minimumVersion = 1.5.2; + }; + }; + A51CDBE82B6D25C9009B6D4E /* XCRemoteSwiftPackageReference "analytics-swift-firebase" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/segment-integrations/analytics-swift-firebase"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.3.4; + }; + }; + A51CDBEB2B6D2BEE009B6D4E /* XCRemoteSwiftPackageReference "braze-segment-swift" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/braze-inc/braze-segment-swift"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.2.0; }; }; BA3042772B1F7147009B64B7 /* XCRemoteSwiftPackageReference "microsoft-authentication-library-for-objc" */ = { @@ -1172,20 +1193,25 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - A5BECBCF2B67C161001776CD /* BrazeKit */ = { + A51CDBE42B6D1E93009B6D4E /* Segment */ = { + isa = XCSwiftPackageProductDependency; + package = A51CDBE32B6D1E93009B6D4E /* XCRemoteSwiftPackageReference "analytics-swift" */; + productName = Segment; + }; + A51CDBE92B6D25C9009B6D4E /* SegmentFirebase */ = { isa = XCSwiftPackageProductDependency; - package = A5BECBCE2B67C161001776CD /* XCRemoteSwiftPackageReference "braze-swift-sdk" */; - productName = BrazeKit; + package = A51CDBE82B6D25C9009B6D4E /* XCRemoteSwiftPackageReference "analytics-swift-firebase" */; + productName = SegmentFirebase; }; - A5BECBD12B67C161001776CD /* BrazeKitCompat */ = { + A51CDBEC2B6D2BEE009B6D4E /* SegmentBraze */ = { isa = XCSwiftPackageProductDependency; - package = A5BECBCE2B67C161001776CD /* XCRemoteSwiftPackageReference "braze-swift-sdk" */; - productName = BrazeKitCompat; + package = A51CDBEB2B6D2BEE009B6D4E /* XCRemoteSwiftPackageReference "braze-segment-swift" */; + productName = SegmentBraze; }; - A5BECBD32B67C161001776CD /* BrazeLocation */ = { + A51CDBEE2B6D2BEE009B6D4E /* SegmentBrazeUI */ = { isa = XCSwiftPackageProductDependency; - package = A5BECBCE2B67C161001776CD /* XCRemoteSwiftPackageReference "braze-swift-sdk" */; - productName = BrazeLocation; + package = A51CDBEB2B6D2BEE009B6D4E /* XCRemoteSwiftPackageReference "braze-segment-swift" */; + productName = SegmentBrazeUI; }; BA3042782B1F7147009B64B7 /* MSAL */ = { isa = XCSwiftPackageProductDependency; diff --git a/OpenEdX/AppDelegate.swift b/OpenEdX/AppDelegate.swift index 567dd5899..c101a7c3d 100644 --- a/OpenEdX/AppDelegate.swift +++ b/OpenEdX/AppDelegate.swift @@ -9,14 +9,16 @@ import UIKit import Core import Swinject import FirebaseCore -import FirebaseAnalytics +//import FirebaseAnalytics import FirebaseCrashlytics import Profile import GoogleSignIn import FacebookCore import MSAL import Theme -import BrazeKit +//import BrazeKit +import Segment +import SegmentFirebase @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -25,7 +27,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { UIApplication.shared.delegate as! AppDelegate } - var braze: Braze? +// var braze: Braze? + var analytics: Analytics? var window: UIWindow? @@ -52,6 +55,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate { ) } configureDeepLinkServices(launchOptions: launchOptions) + if config.segment.enabled { + let configuration = Configuration(writeKey: config.segment.writeKey) + .trackApplicationLifecycleEvents(true) + .flushInterval(10) + analytics = Analytics(configuration: configuration) + if config.firebase.isAnalyticsSourceSegment { + analytics?.add(plugin: FirebaseDestination()) + } + } } Theme.Fonts.registerFonts() diff --git a/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift b/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift index ecb74b036..8d106c2af 100644 --- a/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift +++ b/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift @@ -7,13 +7,14 @@ import Foundation import Core -import FirebaseAnalytics +//import FirebaseAnalytics import Authorization import Discovery import Dashboard import Profile import Course import Discussion +import UIKit class AnalyticsManager: AuthorizationAnalytics, MainScreenAnalytics, @@ -23,8 +24,12 @@ class AnalyticsManager: AuthorizationAnalytics, CourseAnalytics, DiscussionAnalytics { - public func setUserID(_ id: String) { - Analytics.setUserID(id) + public func identify(id: String, username: String, email: String) { + let traits : [String: String] = [ + "email": email, + "username": username + ] + (UIApplication.shared.delegate as? AppDelegate)?.analytics?.identify(userId: id, traits: traits) } public func userLogin(method: AuthMethod) { @@ -309,7 +314,10 @@ class AnalyticsManager: AuthorizationAnalytics, } private func logEvent(_ event: Event, parameters: [String: Any]? = nil) { - Analytics.logEvent(event.rawValue, parameters: parameters) + (UIApplication.shared.delegate as? AppDelegate)?.analytics?.track( + name: event.rawValue, + properties: parameters + ) } } diff --git a/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift b/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift index 01d4d9f99..5d6121f20 100644 --- a/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift +++ b/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift @@ -6,18 +6,22 @@ // import Foundation -import BrazeKit +//import BrazeKit import UIKit +import Segment +import SegmentBrazeUI class BrazeProvider: PushNotificationsProvider { func didRegisterForRemoteNotificationsWithDeviceToken(deviceToken: Data) { - let configuration = Braze.Configuration( - apiKey: "", - endpoint: "" + (UIApplication.shared.delegate as? AppDelegate)?.analytics?.add( + plugin: BrazeDestination( + additionalConfiguration: { configuration in + configuration.logger.level = .debug + }, additionalSetup: { braze in + braze.notifications.register(deviceToken: deviceToken) + } + ) ) - let braze = Braze(configuration: configuration) - braze.notifications.register(deviceToken: deviceToken) - (UIApplication.shared.delegate as? AppDelegate)?.braze = braze } func didFailToRegisterForRemoteNotificationsWithError(error: Error) { } diff --git a/OpenEdX/RouteController.swift b/OpenEdX/RouteController.swift index 70bae693c..499992615 100644 --- a/OpenEdX/RouteController.swift +++ b/OpenEdX/RouteController.swift @@ -30,7 +30,7 @@ class RouteController: UIViewController { super.viewDidLoad() if let user = appStorage.user, appStorage.accessToken != nil { - analytics.setUserID("\(user.id)") + analytics.identify(id: "\(user.id)", username: user.username ?? "", email: user.email ?? "") DispatchQueue.main.async { self.showMainOrWhatsNewScreen() } diff --git a/Podfile b/Podfile index 291bcb28e..c14e319bd 100644 --- a/Podfile +++ b/Podfile @@ -17,7 +17,7 @@ abstract_target "App" do project './Core/Core.xcodeproj' workspace './Core/Core.xcodeproj' #Firebase - pod 'FirebaseAnalytics', '~> 10.11' +# pod 'FirebaseAnalytics', '~> 10.11' pod 'FirebaseCrashlytics', '~> 10.11' #Networking pod 'Alamofire', '~> 5.7' diff --git a/Podfile.lock b/Podfile.lock index fadc65f3b..706da0909 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,23 +1,5 @@ PODS: - Alamofire (5.8.0) - - FirebaseAnalytics (10.15.0): - - FirebaseAnalytics/AdIdSupport (= 10.15.0) - - FirebaseCore (~> 10.0) - - FirebaseInstallations (~> 10.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - - GoogleUtilities/MethodSwizzler (~> 7.11) - - GoogleUtilities/Network (~> 7.11) - - "GoogleUtilities/NSData+zlib (~> 7.11)" - - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseAnalytics/AdIdSupport (10.15.0): - - FirebaseCore (~> 10.0) - - FirebaseInstallations (~> 10.0) - - GoogleAppMeasurement (= 10.15.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - - GoogleUtilities/MethodSwizzler (~> 7.11) - - GoogleUtilities/Network (~> 7.11) - - "GoogleUtilities/NSData+zlib (~> 7.11)" - - nanopb (< 2.30910.0, >= 2.30908.0) - FirebaseCore (10.15.0): - FirebaseCoreInternal (~> 10.0) - GoogleUtilities/Environment (~> 7.8) @@ -34,7 +16,7 @@ PODS: - GoogleUtilities/Environment (~> 7.8) - nanopb (< 2.30910.0, >= 2.30908.0) - PromisesObjC (~> 2.1) - - FirebaseInstallations (10.15.0): + - FirebaseInstallations (10.17.0): - FirebaseCore (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - GoogleUtilities/UserDefaults (~> 7.8) @@ -47,56 +29,24 @@ PODS: - GoogleUtilities/Environment (~> 7.10) - nanopb (< 2.30910.0, >= 2.30908.0) - PromisesSwift (~> 2.1) - - GoogleAppMeasurement (10.15.0): - - GoogleAppMeasurement/AdIdSupport (= 10.15.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - - GoogleUtilities/MethodSwizzler (~> 7.11) - - GoogleUtilities/Network (~> 7.11) - - "GoogleUtilities/NSData+zlib (~> 7.11)" - - nanopb (< 2.30910.0, >= 2.30908.0) - - GoogleAppMeasurement/AdIdSupport (10.15.0): - - GoogleAppMeasurement/WithoutAdIdSupport (= 10.15.0) - - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - - GoogleUtilities/MethodSwizzler (~> 7.11) - - GoogleUtilities/Network (~> 7.11) - - "GoogleUtilities/NSData+zlib (~> 7.11)" - - nanopb (< 2.30910.0, >= 2.30908.0) - - GoogleAppMeasurement/WithoutAdIdSupport (10.15.0): - - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - - GoogleUtilities/MethodSwizzler (~> 7.11) - - GoogleUtilities/Network (~> 7.11) - - "GoogleUtilities/NSData+zlib (~> 7.11)" - - nanopb (< 2.30910.0, >= 2.30908.0) - GoogleDataTransport (9.2.5): - GoogleUtilities/Environment (~> 7.7) - nanopb (< 2.30910.0, >= 2.30908.0) - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/AppDelegateSwizzler (7.11.5): - - GoogleUtilities/Environment - - GoogleUtilities/Logger - - GoogleUtilities/Network - GoogleUtilities/Environment (7.11.5): - PromisesObjC (< 3.0, >= 1.2) - GoogleUtilities/Logger (7.11.5): - GoogleUtilities/Environment - - GoogleUtilities/MethodSwizzler (7.11.5): - - GoogleUtilities/Logger - - GoogleUtilities/Network (7.11.5): - - GoogleUtilities/Logger - - "GoogleUtilities/NSData+zlib" - - GoogleUtilities/Reachability - "GoogleUtilities/NSData+zlib (7.11.5)" - - GoogleUtilities/Reachability (7.11.5): - - GoogleUtilities/Logger - GoogleUtilities/UserDefaults (7.11.5): - GoogleUtilities/Logger - KeychainSwift (20.0.0) - Kingfisher (7.9.1) - - nanopb (2.30909.0): - - nanopb/decode (= 2.30909.0) - - nanopb/encode (= 2.30909.0) - - nanopb/decode (2.30909.0) - - nanopb/encode (2.30909.0) + - nanopb (2.30909.1): + - nanopb/decode (= 2.30909.1) + - nanopb/encode (= 2.30909.1) + - nanopb/decode (2.30909.1) + - nanopb/encode (2.30909.1) - PromisesObjC (2.3.1) - PromisesSwift (2.3.1): - PromisesObjC (= 2.3.1) @@ -112,7 +62,6 @@ PODS: DEPENDENCIES: - Alamofire (~> 5.7) - - FirebaseAnalytics (~> 10.11) - FirebaseCrashlytics (~> 10.11) - KeychainSwift (~> 20.0) - Kingfisher (~> 7.8) @@ -125,14 +74,12 @@ DEPENDENCIES: SPEC REPOS: trunk: - Alamofire - - FirebaseAnalytics - FirebaseCore - FirebaseCoreExtension - FirebaseCoreInternal - FirebaseCrashlytics - FirebaseInstallations - FirebaseSessions - - GoogleAppMeasurement - GoogleDataTransport - GoogleUtilities - KeychainSwift @@ -158,19 +105,17 @@ CHECKOUT OPTIONS: SPEC CHECKSUMS: Alamofire: 0e92e751b3e9e66d7982db43919d01f313b8eb91 - FirebaseAnalytics: 47cef43728f81a839cf1306576bdd77ffa2eac7e FirebaseCore: 2cec518b43635f96afe7ac3a9c513e47558abd2e FirebaseCoreExtension: d3f1ea3725fb41f56e8fbfb29eeaff54e7ffb8f6 FirebaseCoreInternal: 2f4bee5ed00301b5e56da0849268797a2dd31fb4 FirebaseCrashlytics: a83f26fb922a3fe181eb738fb4dcf0c92bba6455 - FirebaseInstallations: cae95cab0f965ce05b805189de1d4c70b11c76fb + FirebaseInstallations: 9387bf15abfc69a714f54e54f74a251264fdb79b FirebaseSessions: ee59a7811bef4c15f65ef6472f3210faa293f9c8 - GoogleAppMeasurement: 722db6550d1e6d552b08398b69a975ac61039338 GoogleDataTransport: 54dee9d48d14580407f8f5fbf2f496e92437a2f2 GoogleUtilities: 13e2c67ede716b8741c7989e26893d151b2b2084 KeychainSwift: 0ce6a4d13f7228054d1a71bb1b500448fb2ab837 Kingfisher: 1d14e9f59cbe19389f591c929000332bf70efd32 - nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431 + nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5 PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4 PromisesSwift: 28dca69a9c40779916ac2d6985a0192a5cb4a265 Sourcery: 6f5fe49b82b7e02e8c65560cbd52e1be67a1af2e @@ -180,6 +125,6 @@ SPEC CHECKSUMS: SwiftyMocky: c5e96e4ff76ec6dbf5a5941aeb039b5a546954a0 Swinject: 893c9a543000ac2f10ee4cbaf0933c6992c935d5 -PODFILE CHECKSUM: 544edab2f9ecc4ac18973fb8865f1d0613ec8a28 +PODFILE CHECKSUM: 9913958fea91fd668f5c10ff6d551637e7f3651c COCOAPODS: 1.14.2 From 2d2ade2265298aa0db94bd454d7e36d9499092da Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Tue, 6 Feb 2024 14:42:31 +0100 Subject: [PATCH 05/21] chore: returned back default values --- Podfile.lock | 2 +- default_config/config_settings.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Podfile.lock b/Podfile.lock index 706da0909..451858fd5 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -127,4 +127,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 9913958fea91fd668f5c10ff6d551637e7f3651c -COCOAPODS: 1.14.2 +COCOAPODS: 1.15.0 diff --git a/default_config/config_settings.yaml b/default_config/config_settings.yaml index c63b34959..249e93fc3 100644 --- a/default_config/config_settings.yaml +++ b/default_config/config_settings.yaml @@ -1,4 +1,4 @@ -config_directory: './../edx-mobile-config' +config_directory: './default_config' config_mapping: prod: 'prod' stage: 'stage' From c1644951e7baef99e7c6c8fe81a87cdedb9d771e Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Tue, 6 Feb 2024 18:25:18 +0100 Subject: [PATCH 06/21] chore: added firebase as swift package --- Core/Core.xcodeproj/project.pbxproj | 33 ++++++++- .../Configuration/Config/FirebaseConfig.swift | 1 + OpenEdX.xcodeproj/project.pbxproj | 17 ----- OpenEdX/AppDelegate.swift | 8 +- .../AnalyticsManager/AnalyticsManager.swift | 6 +- Podfile | 2 +- Podfile.lock | 73 +------------------ 7 files changed, 40 insertions(+), 100 deletions(-) diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index d6fe7602f..c7faf4304 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -124,13 +124,15 @@ 0770DE6128D0B2CB006D8A5D /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE6028D0B2CB006D8A5D /* Assets.swift */; }; 07DDFCBD29A780BB00572595 /* UINavigationController+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07DDFCBC29A780BB00572595 /* UINavigationController+Animation.swift */; }; 07E0939F2B308D2800F1E4B2 /* Data_Certificate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07E0939E2B308D2800F1E4B2 /* Data_Certificate.swift */; }; + A51188632B729647004E9F8E /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = A51188622B729647004E9F8E /* FirebaseAnalytics */; }; + A51188652B72964F004E9F8E /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = A51188642B72964F004E9F8E /* FirebaseCrashlytics */; }; A51CDBE72B6D21F2009B6D4E /* SegmentConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51CDBE62B6D21F2009B6D4E /* SegmentConfig.swift */; }; A53A32352B233DEC005FE38A /* ThemeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A32342B233DEC005FE38A /* ThemeConfig.swift */; }; A595689B2B6173DF00ED4F90 /* BranchConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A595689A2B6173DF00ED4F90 /* BranchConfig.swift */; }; A5F4E7B52B61544A00ACD166 /* BrazeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F4E7B42B61544A00ACD166 /* BrazeConfig.swift */; }; BA30427F2B20B320009B64B7 /* SocialAuthError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA30427D2B20B299009B64B7 /* SocialAuthError.swift */; }; - BA4AFB442B6A5AF100A21367 /* CheckBoxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4AFB432B6A5AF100A21367 /* CheckBoxView.swift */; }; BA4AFB422B5A7A0900A21367 /* VideoDownloadQualityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4AFB412B5A7A0900A21367 /* VideoDownloadQualityView.swift */; }; + BA4AFB442B6A5AF100A21367 /* CheckBoxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4AFB432B6A5AF100A21367 /* CheckBoxView.swift */; }; BA593F1C2AF8E498009ADB51 /* ScrollSlidingTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA593F1B2AF8E498009ADB51 /* ScrollSlidingTabBar.swift */; }; BA593F1E2AF8E4A0009ADB51 /* FrameReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA593F1D2AF8E4A0009ADB51 /* FrameReader.swift */; }; BA76135C2B21BC7300B599B7 /* SocialAuthResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA76135B2B21BC7300B599B7 /* SocialAuthResponse.swift */; }; @@ -141,9 +143,9 @@ BA8FA66C2AD59BBC00EA029A /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = BA8FA66B2AD59BBC00EA029A /* GoogleSignIn */; }; BA8FA66E2AD59E7D00EA029A /* FacebookAuthProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA66D2AD59E7D00EA029A /* FacebookAuthProvider.swift */; }; BA8FA6702AD59EA300EA029A /* MicrosoftAuthProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA66F2AD59EA300EA029A /* MicrosoftAuthProvider.swift */; }; + BAAD62C62AFCF00B000E6103 /* CustomDisclosureGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAAD62C52AFCF00B000E6103 /* CustomDisclosureGroup.swift */; }; BAD9CA2F2B289B3500DE790A /* ajaxHandler.js in Resources */ = {isa = PBXBuildFile; fileRef = BAD9CA2E2B289B3500DE790A /* ajaxHandler.js */; }; BAD9CA332B28A8F300DE790A /* AjaxProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAD9CA322B28A8F300DE790A /* AjaxProvider.swift */; }; - BAAD62C62AFCF00B000E6103 /* CustomDisclosureGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAAD62C52AFCF00B000E6103 /* CustomDisclosureGroup.swift */; }; BAD9CA422B2B140100DE790A /* AgreementConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAD9CA412B2B140100DE790A /* AgreementConfigTests.swift */; }; BADB3F5B2AD6EC56004D5CFA /* ResultExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BADB3F5A2AD6EC56004D5CFA /* ResultExtension.swift */; }; BAF0D4CB2AD6AE14007AC334 /* FacebookLogin in Frameworks */ = {isa = PBXBuildFile; productRef = BAF0D4CA2AD6AE14007AC334 /* FacebookLogin */; }; @@ -305,8 +307,8 @@ A595689A2B6173DF00ED4F90 /* BranchConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BranchConfig.swift; sourceTree = ""; }; A5F4E7B42B61544A00ACD166 /* BrazeConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrazeConfig.swift; sourceTree = ""; }; BA30427D2B20B299009B64B7 /* SocialAuthError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocialAuthError.swift; sourceTree = ""; }; - BA4AFB432B6A5AF100A21367 /* CheckBoxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckBoxView.swift; sourceTree = ""; }; BA4AFB412B5A7A0900A21367 /* VideoDownloadQualityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoDownloadQualityView.swift; sourceTree = ""; }; + BA4AFB432B6A5AF100A21367 /* CheckBoxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckBoxView.swift; sourceTree = ""; }; BA593F1B2AF8E498009ADB51 /* ScrollSlidingTabBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollSlidingTabBar.swift; sourceTree = ""; }; BA593F1D2AF8E4A0009ADB51 /* FrameReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FrameReader.swift; sourceTree = ""; }; BA76135B2B21BC7300B599B7 /* SocialAuthResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialAuthResponse.swift; sourceTree = ""; }; @@ -316,9 +318,9 @@ BA8FA6692AD59B5500EA029A /* GoogleAuthProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleAuthProvider.swift; sourceTree = ""; }; BA8FA66D2AD59E7D00EA029A /* FacebookAuthProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FacebookAuthProvider.swift; sourceTree = ""; }; BA8FA66F2AD59EA300EA029A /* MicrosoftAuthProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosoftAuthProvider.swift; sourceTree = ""; }; + BAAD62C52AFCF00B000E6103 /* CustomDisclosureGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDisclosureGroup.swift; sourceTree = ""; }; BAD9CA2E2B289B3500DE790A /* ajaxHandler.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = ajaxHandler.js; sourceTree = ""; }; BAD9CA322B28A8F300DE790A /* AjaxProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AjaxProvider.swift; sourceTree = ""; }; - BAAD62C52AFCF00B000E6103 /* CustomDisclosureGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDisclosureGroup.swift; sourceTree = ""; }; BAD9CA412B2B140100DE790A /* AgreementConfigTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgreementConfigTests.swift; sourceTree = ""; }; BADB3F5A2AD6EC56004D5CFA /* ResultExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultExtension.swift; sourceTree = ""; }; BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FacebookConfig.swift; sourceTree = ""; }; @@ -352,6 +354,8 @@ files = ( BAF0D4CB2AD6AE14007AC334 /* FacebookLogin in Frameworks */, 025EF2F62971740000B838AB /* YouTubePlayerKit in Frameworks */, + A51188632B729647004E9F8E /* FirebaseAnalytics in Frameworks */, + A51188652B72964F004E9F8E /* FirebaseCrashlytics in Frameworks */, C8C446EF233F81B9FABB77D2 /* Pods_App_Core.framework in Frameworks */, BA8FA66C2AD59BBC00EA029A /* GoogleSignIn in Frameworks */, E055A5392B18DC95008D9E5E /* Theme.framework in Frameworks */, @@ -867,6 +871,8 @@ 025EF2F52971740000B838AB /* YouTubePlayerKit */, BA8FA66B2AD59BBC00EA029A /* GoogleSignIn */, BAF0D4CA2AD6AE14007AC334 /* FacebookLogin */, + A51188622B729647004E9F8E /* FirebaseAnalytics */, + A51188642B72964F004E9F8E /* FirebaseCrashlytics */, ); productName = Core; productReference = 0770DE0828D07831006D8A5D /* Core.framework */; @@ -906,6 +912,7 @@ 025EF2F42971740000B838AB /* XCRemoteSwiftPackageReference "YouTubePlayerKit" */, BA8FA65E2AD574D700EA029A /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */, BA8FA6712AD6ABA300EA029A /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */, + A51188612B7295F7004E9F8E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, ); productRefGroup = 0770DE0928D07831006D8A5D /* Products */; projectDirPath = ""; @@ -2160,6 +2167,14 @@ minimumVersion = 1.5.0; }; }; + A51188612B7295F7004E9F8E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 10.20.0; + }; + }; BA8FA65E2AD574D700EA029A /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/google/GoogleSignIn-iOS"; @@ -2184,6 +2199,16 @@ package = 025EF2F42971740000B838AB /* XCRemoteSwiftPackageReference "YouTubePlayerKit" */; productName = YouTubePlayerKit; }; + A51188622B729647004E9F8E /* FirebaseAnalytics */ = { + isa = XCSwiftPackageProductDependency; + package = A51188612B7295F7004E9F8E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseAnalytics; + }; + A51188642B72964F004E9F8E /* FirebaseCrashlytics */ = { + isa = XCSwiftPackageProductDependency; + package = A51188612B7295F7004E9F8E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseCrashlytics; + }; BA8FA66B2AD59BBC00EA029A /* GoogleSignIn */ = { isa = XCSwiftPackageProductDependency; package = BA8FA65E2AD574D700EA029A /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */; diff --git a/Core/Core/Configuration/Config/FirebaseConfig.swift b/Core/Core/Configuration/Config/FirebaseConfig.swift index ba4a6f020..e8180f574 100644 --- a/Core/Core/Configuration/Config/FirebaseConfig.swift +++ b/Core/Core/Configuration/Config/FirebaseConfig.swift @@ -91,6 +91,7 @@ public final class FirebaseConfig: NSObject { firebaseOptions.clientID = clientID firebaseOptions.storageBucket = storageBucket firebaseOptions.databaseURL = databaseURL + return firebaseOptions } return nil diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index 13265bdee..494ac21c3 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -51,7 +51,6 @@ A50066932B614DCD0024680B /* FCMListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50066922B614DCD0024680B /* FCMListener.swift */; }; A50066952B614DEF0024680B /* BrazeListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50066942B614DEF0024680B /* BrazeListener.swift */; }; A51CDBE52B6D1E93009B6D4E /* Segment in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBE42B6D1E93009B6D4E /* Segment */; }; - A51CDBEA2B6D25C9009B6D4E /* SegmentFirebase in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBE92B6D25C9009B6D4E /* SegmentFirebase */; }; A51CDBED2B6D2BEE009B6D4E /* SegmentBraze in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBEC2B6D2BEE009B6D4E /* SegmentBraze */; }; A51CDBEF2B6D2BEE009B6D4E /* SegmentBrazeUI in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBEE2B6D2BEE009B6D4E /* SegmentBrazeUI */; }; A59568952B61630500ED4F90 /* DeepLinkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568942B61630500ED4F90 /* DeepLinkManager.swift */; }; @@ -157,7 +156,6 @@ A51CDBE52B6D1E93009B6D4E /* Segment in Frameworks */, 072787B128D34D83002E9142 /* Discovery.framework in Frameworks */, 0218196428F734FA00202564 /* Discussion.framework in Frameworks */, - A51CDBEA2B6D25C9009B6D4E /* SegmentFirebase in Frameworks */, 0219C67728F4347600D64452 /* Course.framework in Frameworks */, 027DB33028D8A063002B6862 /* Dashboard.framework in Frameworks */, 95C140F3BDF778364986E83B /* Pods_App_OpenEdX.framework in Frameworks */, @@ -363,7 +361,6 @@ packageProductDependencies = ( BA3042782B1F7147009B64B7 /* MSAL */, A51CDBE42B6D1E93009B6D4E /* Segment */, - A51CDBE92B6D25C9009B6D4E /* SegmentFirebase */, A51CDBEC2B6D2BEE009B6D4E /* SegmentBraze */, A51CDBEE2B6D2BEE009B6D4E /* SegmentBrazeUI */, ); @@ -399,7 +396,6 @@ packageReferences = ( BA3042772B1F7147009B64B7 /* XCRemoteSwiftPackageReference "microsoft-authentication-library-for-objc" */, A51CDBE32B6D1E93009B6D4E /* XCRemoteSwiftPackageReference "analytics-swift" */, - A51CDBE82B6D25C9009B6D4E /* XCRemoteSwiftPackageReference "analytics-swift-firebase" */, A51CDBEB2B6D2BEE009B6D4E /* XCRemoteSwiftPackageReference "braze-segment-swift" */, ); productRefGroup = 07D5DA3228D075AA00752FD9 /* Products */; @@ -1166,14 +1162,6 @@ minimumVersion = 1.5.2; }; }; - A51CDBE82B6D25C9009B6D4E /* XCRemoteSwiftPackageReference "analytics-swift-firebase" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/segment-integrations/analytics-swift-firebase"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.3.4; - }; - }; A51CDBEB2B6D2BEE009B6D4E /* XCRemoteSwiftPackageReference "braze-segment-swift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/braze-inc/braze-segment-swift"; @@ -1198,11 +1186,6 @@ package = A51CDBE32B6D1E93009B6D4E /* XCRemoteSwiftPackageReference "analytics-swift" */; productName = Segment; }; - A51CDBE92B6D25C9009B6D4E /* SegmentFirebase */ = { - isa = XCSwiftPackageProductDependency; - package = A51CDBE82B6D25C9009B6D4E /* XCRemoteSwiftPackageReference "analytics-swift-firebase" */; - productName = SegmentFirebase; - }; A51CDBEC2B6D2BEE009B6D4E /* SegmentBraze */ = { isa = XCSwiftPackageProductDependency; package = A51CDBEB2B6D2BEE009B6D4E /* XCRemoteSwiftPackageReference "braze-segment-swift" */; diff --git a/OpenEdX/AppDelegate.swift b/OpenEdX/AppDelegate.swift index 53dada485..6e15bd11d 100644 --- a/OpenEdX/AppDelegate.swift +++ b/OpenEdX/AppDelegate.swift @@ -18,7 +18,7 @@ import MSAL import Theme //import BrazeKit import Segment -import SegmentFirebase +//import SegmentFirebase @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -60,9 +60,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { .trackApplicationLifecycleEvents(true) .flushInterval(10) analytics = Analytics(configuration: configuration) - if config.firebase.isAnalyticsSourceSegment { - analytics?.add(plugin: FirebaseDestination()) - } +// if config.firebase.isAnalyticsSourceSegment { +// analytics?.add(plugin: FirebaseDestination()) +// } } } diff --git a/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift b/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift index 8d106c2af..9e3aa6e7f 100644 --- a/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift +++ b/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift @@ -7,7 +7,7 @@ import Foundation import Core -//import FirebaseAnalytics +import FirebaseAnalytics import Authorization import Discovery import Dashboard @@ -25,7 +25,8 @@ class AnalyticsManager: AuthorizationAnalytics, DiscussionAnalytics { public func identify(id: String, username: String, email: String) { - let traits : [String: String] = [ + Analytics.setUserID(id) + let traits: [String: String] = [ "email": email, "username": username ] @@ -314,6 +315,7 @@ class AnalyticsManager: AuthorizationAnalytics, } private func logEvent(_ event: Event, parameters: [String: Any]? = nil) { + Analytics.logEvent(event.rawValue, parameters: parameters) (UIApplication.shared.delegate as? AppDelegate)?.analytics?.track( name: event.rawValue, properties: parameters diff --git a/Podfile b/Podfile index c14e319bd..0a785d68f 100644 --- a/Podfile +++ b/Podfile @@ -18,7 +18,7 @@ abstract_target "App" do workspace './Core/Core.xcodeproj' #Firebase # pod 'FirebaseAnalytics', '~> 10.11' - pod 'FirebaseCrashlytics', '~> 10.11' +# pod 'FirebaseCrashlytics', '~> 10.11' #Networking pod 'Alamofire', '~> 5.7' #Keychain diff --git a/Podfile.lock b/Podfile.lock index 451858fd5..efae391c2 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,55 +1,7 @@ PODS: - Alamofire (5.8.0) - - FirebaseCore (10.15.0): - - FirebaseCoreInternal (~> 10.0) - - GoogleUtilities/Environment (~> 7.8) - - GoogleUtilities/Logger (~> 7.8) - - FirebaseCoreExtension (10.15.0): - - FirebaseCore (~> 10.0) - - FirebaseCoreInternal (10.15.0): - - "GoogleUtilities/NSData+zlib (~> 7.8)" - - FirebaseCrashlytics (10.15.0): - - FirebaseCore (~> 10.5) - - FirebaseInstallations (~> 10.0) - - FirebaseSessions (~> 10.5) - - GoogleDataTransport (~> 9.2) - - GoogleUtilities/Environment (~> 7.8) - - nanopb (< 2.30910.0, >= 2.30908.0) - - PromisesObjC (~> 2.1) - - FirebaseInstallations (10.17.0): - - FirebaseCore (~> 10.0) - - GoogleUtilities/Environment (~> 7.8) - - GoogleUtilities/UserDefaults (~> 7.8) - - PromisesObjC (~> 2.1) - - FirebaseSessions (10.15.0): - - FirebaseCore (~> 10.5) - - FirebaseCoreExtension (~> 10.0) - - FirebaseInstallations (~> 10.0) - - GoogleDataTransport (~> 9.2) - - GoogleUtilities/Environment (~> 7.10) - - nanopb (< 2.30910.0, >= 2.30908.0) - - PromisesSwift (~> 2.1) - - GoogleDataTransport (9.2.5): - - GoogleUtilities/Environment (~> 7.7) - - nanopb (< 2.30910.0, >= 2.30908.0) - - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/Environment (7.11.5): - - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/Logger (7.11.5): - - GoogleUtilities/Environment - - "GoogleUtilities/NSData+zlib (7.11.5)" - - GoogleUtilities/UserDefaults (7.11.5): - - GoogleUtilities/Logger - KeychainSwift (20.0.0) - Kingfisher (7.9.1) - - nanopb (2.30909.1): - - nanopb/decode (= 2.30909.1) - - nanopb/encode (= 2.30909.1) - - nanopb/decode (2.30909.1) - - nanopb/encode (2.30909.1) - - PromisesObjC (2.3.1) - - PromisesSwift (2.3.1): - - PromisesObjC (= 2.3.1) - Sourcery (1.8.0): - Sourcery/CLI-Only (= 1.8.0) - Sourcery/CLI-Only (1.8.0) @@ -62,7 +14,6 @@ PODS: DEPENDENCIES: - Alamofire (~> 5.7) - - FirebaseCrashlytics (~> 10.11) - KeychainSwift (~> 20.0) - Kingfisher (~> 7.8) - SwiftGen (~> 6.6) @@ -74,19 +25,8 @@ DEPENDENCIES: SPEC REPOS: trunk: - Alamofire - - FirebaseCore - - FirebaseCoreExtension - - FirebaseCoreInternal - - FirebaseCrashlytics - - FirebaseInstallations - - FirebaseSessions - - GoogleDataTransport - - GoogleUtilities - KeychainSwift - Kingfisher - - nanopb - - PromisesObjC - - PromisesSwift - Sourcery - SwiftGen - SwiftLint @@ -105,19 +45,8 @@ CHECKOUT OPTIONS: SPEC CHECKSUMS: Alamofire: 0e92e751b3e9e66d7982db43919d01f313b8eb91 - FirebaseCore: 2cec518b43635f96afe7ac3a9c513e47558abd2e - FirebaseCoreExtension: d3f1ea3725fb41f56e8fbfb29eeaff54e7ffb8f6 - FirebaseCoreInternal: 2f4bee5ed00301b5e56da0849268797a2dd31fb4 - FirebaseCrashlytics: a83f26fb922a3fe181eb738fb4dcf0c92bba6455 - FirebaseInstallations: 9387bf15abfc69a714f54e54f74a251264fdb79b - FirebaseSessions: ee59a7811bef4c15f65ef6472f3210faa293f9c8 - GoogleDataTransport: 54dee9d48d14580407f8f5fbf2f496e92437a2f2 - GoogleUtilities: 13e2c67ede716b8741c7989e26893d151b2b2084 KeychainSwift: 0ce6a4d13f7228054d1a71bb1b500448fb2ab837 Kingfisher: 1d14e9f59cbe19389f591c929000332bf70efd32 - nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5 - PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4 - PromisesSwift: 28dca69a9c40779916ac2d6985a0192a5cb4a265 Sourcery: 6f5fe49b82b7e02e8c65560cbd52e1be67a1af2e SwiftGen: 1366a7f71aeef49954ca5a63ba4bef6b0f24138c SwiftLint: 5ce4d6a8ff83f1b5fd5ad5dbf30965d35af65e44 @@ -125,6 +54,6 @@ SPEC CHECKSUMS: SwiftyMocky: c5e96e4ff76ec6dbf5a5941aeb039b5a546954a0 Swinject: 893c9a543000ac2f10ee4cbaf0933c6992c935d5 -PODFILE CHECKSUM: 9913958fea91fd668f5c10ff6d551637e7f3651c +PODFILE CHECKSUM: b4e49ed566507f9bbf9bcff18accdca28f28e106 COCOAPODS: 1.15.0 From f9c681335c20159bb6e359e9640bcb36d8ad530d Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Wed, 7 Feb 2024 15:07:57 +0100 Subject: [PATCH 07/21] chore: fixed upload crashlytic dsym build phase script --- OpenEdX.xcodeproj/project.pbxproj | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index 494ac21c3..aacd4d74e 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -431,6 +431,11 @@ inputFileListPaths = ( ); inputPaths = ( + "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}", + "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${PRODUCT_NAME}", + "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist", + "$(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/GoogleService-Info.plist", + "$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)", ); name = FirebaseCrashlytics; outputFileListPaths = ( @@ -439,7 +444,7 @@ ); runOnlyForDeploymentPostprocessing = 1; shellPath = /bin/sh; - shellScript = "plistPath=\"${BUILT_PRODUCTS_DIR}/${WRAPPER_NAME}/GoogleService-Info.plist\"\n\ngoogleAppID=$(/usr/libexec/PlistBuddy -c \"Print :GOOGLE_APP_ID\" \"$plistPath\")\n\nif [ -z \"$googleAppID\" ]\nthen\n echo \"GoogleAppID is empty. The FirebaseCrashlytics script will be skipped.\"\nelse\n \"${PODS_ROOT}/FirebaseCrashlytics/run\" --app-id \"$googleAppID\" -p ios \"${DWARF_DSYM_FOLDER_PATH}\"\nfi\n"; + shellScript = "plistPath=\"${BUILT_PRODUCTS_DIR}/${WRAPPER_NAME}/GoogleService-Info.plist\"\n\ngoogleAppID=$(/usr/libexec/PlistBuddy -c \"Print :GOOGLE_APP_ID\" \"$plistPath\")\n\nif [ -z \"$googleAppID\" ]\nthen\n echo \"GoogleAppID is empty. The FirebaseCrashlytics script will be skipped.\"\nelse\n \"${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run\"\nfi\n"; }; 0770DE2328D08647006D8A5D /* SwiftLint */ = { isa = PBXShellScriptBuildPhase; From 5a61d28549c6fd2aedbc96eab8560cad35f2c5f0 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Thu, 15 Feb 2024 15:24:23 +0100 Subject: [PATCH 08/21] chore: added AnalyticsService protocol and instances --- OpenEdX.xcodeproj/project.pbxproj | 19 ++++++++- OpenEdX/AppDelegate.swift | 11 +---- OpenEdX/DI/AppAssembly.swift | 6 ++- .../AnalyticsManager/AnalyticsManager.swift | 42 +++++++++++++------ .../Services/GoogleAnalyticsService.swift | 18 ++++++++ .../Services/SegmentAnalyticsService.swift | 26 ++++++++++++ 6 files changed, 96 insertions(+), 26 deletions(-) create mode 100644 OpenEdX/Managers/AnalyticsManager/Services/GoogleAnalyticsService.swift create mode 100644 OpenEdX/Managers/AnalyticsManager/Services/SegmentAnalyticsService.swift diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index 674797359..ad68ce675 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -53,6 +53,8 @@ A51CDBE52B6D1E93009B6D4E /* Segment in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBE42B6D1E93009B6D4E /* Segment */; }; A51CDBED2B6D2BEE009B6D4E /* SegmentBraze in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBEC2B6D2BEE009B6D4E /* SegmentBraze */; }; A51CDBEF2B6D2BEE009B6D4E /* SegmentBrazeUI in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBEE2B6D2BEE009B6D4E /* SegmentBrazeUI */; }; + A52508082B7E4FCC0078E1D8 /* GoogleAnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52508072B7E4FCC0078E1D8 /* GoogleAnalyticsService.swift */; }; + A525080A2B7E500A0078E1D8 /* SegmentAnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52508092B7E500A0078E1D8 /* SegmentAnalyticsService.swift */; }; A59568952B61630500ED4F90 /* DeepLinkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568942B61630500ED4F90 /* DeepLinkManager.swift */; }; A59568972B61653700ED4F90 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568962B61653700ED4F90 /* DeepLink.swift */; }; A59568992B616D9400ED4F90 /* PushLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568982B616D9400ED4F90 /* PushLink.swift */; }; @@ -132,10 +134,12 @@ A50066902B61467B0024680B /* BrazeProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrazeProvider.swift; sourceTree = ""; }; A50066922B614DCD0024680B /* FCMListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCMListener.swift; sourceTree = ""; }; A50066942B614DEF0024680B /* BrazeListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrazeListener.swift; sourceTree = ""; }; + A52508072B7E4FCC0078E1D8 /* GoogleAnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleAnalyticsService.swift; sourceTree = ""; }; + A52508092B7E500A0078E1D8 /* SegmentAnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentAnalyticsService.swift; sourceTree = ""; }; A59568942B61630500ED4F90 /* DeepLinkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkManager.swift; sourceTree = ""; }; A59568962B61653700ED4F90 /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = ""; }; A59568982B616D9400ED4F90 /* PushLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushLink.swift; sourceTree = ""; }; - A59585AE2B62A07100A35A20 /* BranchService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BranchService.swift; sourceTree = ""; }; + A59585AE2B62A07100A35A20 /* BranchService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BranchService.swift; path = ../BranchService.swift; sourceTree = ""; }; A89AD827F52CF6A6B903606E /* Pods-App-OpenEdX.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-OpenEdX.releasestage.xcconfig"; path = "Target Support Files/Pods-App-OpenEdX/Pods-App-OpenEdX.releasestage.xcconfig"; sourceTree = ""; }; BB08ACD2CCA33D6DDDDD31B4 /* Pods-App-OpenEdX.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-OpenEdX.releasedev.xcconfig"; path = "Target Support Files/Pods-App-OpenEdX/Pods-App-OpenEdX.releasedev.xcconfig"; sourceTree = ""; }; E0D6E6A22B1626B10089F9C9 /* Theme.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Theme.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -295,6 +299,7 @@ isa = PBXGroup; children = ( 0298DF2F2A4EF7230023A257 /* AnalyticsManager.swift */, + A52508062B7E4FA90078E1D8 /* Services */, 02F175302A4DA95B0019CD70 /* MainScreenAnalytics.swift */, ); path = AnalyticsManager; @@ -318,11 +323,19 @@ path = Listeners; sourceTree = ""; }; + A52508062B7E4FA90078E1D8 /* Services */ = { + isa = PBXGroup; + children = ( + A52508072B7E4FCC0078E1D8 /* GoogleAnalyticsService.swift */, + A52508092B7E500A0078E1D8 /* SegmentAnalyticsService.swift */, + ); + path = Services; + sourceTree = ""; + }; A59568932B6162E400ED4F90 /* DeepLinkManager */ = { isa = PBXGroup; children = ( A59568942B61630500ED4F90 /* DeepLinkManager.swift */, - A59585AE2B62A07100A35A20 /* BranchService.swift */, A5F46FD02B692B140003EEEF /* Services */, A59585AD2B62677B00A35A20 /* Link */, ); @@ -526,6 +539,8 @@ 020CA5D92AA0A25300970AAF /* AppStorage.swift in Sources */, 0298DF302A4EF7230023A257 /* AnalyticsManager.swift in Sources */, A59585AF2B62A07100A35A20 /* BranchService.swift in Sources */, + A525080A2B7E500A0078E1D8 /* SegmentAnalyticsService.swift in Sources */, + A52508082B7E4FCC0078E1D8 /* GoogleAnalyticsService.swift in Sources */, A50066912B61467B0024680B /* BrazeProvider.swift in Sources */, 0293A2072A6FCDA30090A336 /* DiscoveryPersistence.swift in Sources */, 07D5DA3528D075AA00752FD9 /* AppDelegate.swift in Sources */, diff --git a/OpenEdX/AppDelegate.swift b/OpenEdX/AppDelegate.swift index c13eadbba..6f7ee65f4 100644 --- a/OpenEdX/AppDelegate.swift +++ b/OpenEdX/AppDelegate.swift @@ -9,16 +9,13 @@ import UIKit import Core import Swinject import FirebaseCore -//import FirebaseAnalytics import FirebaseCrashlytics import Profile import GoogleSignIn import FacebookCore import MSAL import Theme -//import BrazeKit import Segment -//import SegmentFirebase @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -27,7 +24,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { UIApplication.shared.delegate as! AppDelegate } -// var braze: Braze? var analytics: Analytics? var window: UIWindow? @@ -60,9 +56,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { .trackApplicationLifecycleEvents(true) .flushInterval(10) analytics = Analytics(configuration: configuration) -// if config.firebase.isAnalyticsSourceSegment { -// analytics?.add(plugin: FirebaseDestination()) -// } } } @@ -145,8 +138,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { guard Date().timeIntervalSince1970 - lastForceLogoutTime > 5 else { return } - let analytics = Container.shared.resolve(AnalyticsManager.self) - analytics?.userLogout(force: true) + let analyticsManager = Container.shared.resolve(AnalyticsManager.self) + analyticsManager?.userLogout(force: true) lastForceLogoutTime = Date().timeIntervalSince1970 diff --git a/OpenEdX/DI/AppAssembly.swift b/OpenEdX/DI/AppAssembly.swift index 8a98db84c..e2ebd1479 100644 --- a/OpenEdX/DI/AppAssembly.swift +++ b/OpenEdX/DI/AppAssembly.swift @@ -35,8 +35,10 @@ class AppAssembly: Assembly { Router(navigationController: r.resolve(UINavigationController.self)!, container: container) } - container.register(AnalyticsManager.self) { _ in - AnalyticsManager() + container.register(AnalyticsManager.self) { r in + AnalyticsManager( + config: r.resolve(ConfigProtocol.self)! + ) } container.register(AuthorizationAnalytics.self) { r in diff --git a/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift b/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift index 9e3aa6e7f..16c0500ed 100644 --- a/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift +++ b/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift @@ -7,14 +7,17 @@ import Foundation import Core -import FirebaseAnalytics import Authorization import Discovery import Dashboard import Profile import Course import Discussion -import UIKit + +protocol AnalyticsService { + func identify(id: String, username: String?, email: String?) + func logEvent(_ event: Event, parameters: [String: Any]?) +} class AnalyticsManager: AuthorizationAnalytics, MainScreenAnalytics, @@ -24,13 +27,28 @@ class AnalyticsManager: AuthorizationAnalytics, CourseAnalytics, DiscussionAnalytics { + private var services: [AnalyticsService] = [] + + // Init Analytics Manager + public init(config: ConfigProtocol) { + services = servicesFor(config: config) + } + + private func servicesFor(config: ConfigProtocol) -> [AnalyticsService] { + var analyticsServices: [AnalyticsService] = [] + // add Google Analytics Service + analyticsServices.append(GoogleAnalyticsService()) + // add Segment Analytics Service if enabled + if config.segment.enabled { + analyticsServices.append(SegmentAnalyticsService()) + } + return analyticsServices + } + public func identify(id: String, username: String, email: String) { - Analytics.setUserID(id) - let traits: [String: String] = [ - "email": email, - "username": username - ] - (UIApplication.shared.delegate as? AppDelegate)?.analytics?.identify(userId: id, traits: traits) + for service in services { + service.identify(id: id, username: username, email: email) + } } public func userLogin(method: AuthMethod) { @@ -315,11 +333,9 @@ class AnalyticsManager: AuthorizationAnalytics, } private func logEvent(_ event: Event, parameters: [String: Any]? = nil) { - Analytics.logEvent(event.rawValue, parameters: parameters) - (UIApplication.shared.delegate as? AppDelegate)?.analytics?.track( - name: event.rawValue, - properties: parameters - ) + for service in services { + service.logEvent(event, parameters: parameters) + } } } diff --git a/OpenEdX/Managers/AnalyticsManager/Services/GoogleAnalyticsService.swift b/OpenEdX/Managers/AnalyticsManager/Services/GoogleAnalyticsService.swift new file mode 100644 index 000000000..608b83ad3 --- /dev/null +++ b/OpenEdX/Managers/AnalyticsManager/Services/GoogleAnalyticsService.swift @@ -0,0 +1,18 @@ +// +// GoogleAnalyticsService.swift +// OpenEdX +// +// Created by Anton Yarmolenka on 15/02/2024. +// + +import Foundation +import FirebaseAnalytics + +class GoogleAnalyticsService: AnalyticsService { + func identify(id: String, username: String?, email: String?) { + Analytics.setUserID(id) + } + func logEvent(_ event: Event, parameters: [String: Any]?) { + Analytics.logEvent(event.rawValue, parameters: parameters) + } +} diff --git a/OpenEdX/Managers/AnalyticsManager/Services/SegmentAnalyticsService.swift b/OpenEdX/Managers/AnalyticsManager/Services/SegmentAnalyticsService.swift new file mode 100644 index 000000000..5515e5bc6 --- /dev/null +++ b/OpenEdX/Managers/AnalyticsManager/Services/SegmentAnalyticsService.swift @@ -0,0 +1,26 @@ +// +// SegmentAnalyticsService.swift +// OpenEdX +// +// Created by Anton Yarmolenka on 15/02/2024. +// + +import Foundation +import UIKit + +class SegmentAnalyticsService: AnalyticsService { + func identify(id: String, username: String?, email: String?) { + guard let email = email, let username = username else { return } + let traits: [String: String] = [ + "email": email, + "username": username + ] + (UIApplication.shared.delegate as? AppDelegate)?.analytics?.identify(userId: id, traits: traits) + } + func logEvent(_ event: Event, parameters: [String: Any]?) { + (UIApplication.shared.delegate as? AppDelegate)?.analytics?.track( + name: event.rawValue, + properties: parameters + ) + } +} From 5d9dd2be71130cfa2302a1d4821b201839e69279 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Thu, 15 Feb 2024 15:45:33 +0100 Subject: [PATCH 09/21] chore: cleanup code --- Core/Core.xcodeproj/project.pbxproj | 2 +- .../PushNotificationsManager/Providers/BrazeProvider.swift | 2 -- Podfile | 3 --- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index a09765787..ac3e61e30 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -125,10 +125,10 @@ 0770DE6128D0B2CB006D8A5D /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE6028D0B2CB006D8A5D /* Assets.swift */; }; 07DDFCBD29A780BB00572595 /* UINavigationController+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07DDFCBC29A780BB00572595 /* UINavigationController+Animation.swift */; }; 07E0939F2B308D2800F1E4B2 /* Data_Certificate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07E0939E2B308D2800F1E4B2 /* Data_Certificate.swift */; }; + 141F1D302B7328D4009E81EB /* WebviewCookiesUpdateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 141F1D2F2B7328D4009E81EB /* WebviewCookiesUpdateProtocol.swift */; }; A51188632B729647004E9F8E /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = A51188622B729647004E9F8E /* FirebaseAnalytics */; }; A51188652B72964F004E9F8E /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = A51188642B72964F004E9F8E /* FirebaseCrashlytics */; }; A51CDBE72B6D21F2009B6D4E /* SegmentConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51CDBE62B6D21F2009B6D4E /* SegmentConfig.swift */; }; - 141F1D302B7328D4009E81EB /* WebviewCookiesUpdateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 141F1D2F2B7328D4009E81EB /* WebviewCookiesUpdateProtocol.swift */; }; A53A32352B233DEC005FE38A /* ThemeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A32342B233DEC005FE38A /* ThemeConfig.swift */; }; A595689B2B6173DF00ED4F90 /* BranchConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A595689A2B6173DF00ED4F90 /* BranchConfig.swift */; }; A5F4E7B52B61544A00ACD166 /* BrazeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F4E7B42B61544A00ACD166 /* BrazeConfig.swift */; }; diff --git a/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift b/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift index f732ba089..b086af806 100644 --- a/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift +++ b/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift @@ -6,9 +6,7 @@ // import Foundation -//import BrazeKit import UIKit -import Segment import SegmentBrazeUI class BrazeProvider: PushNotificationsProvider { diff --git a/Podfile b/Podfile index 0a785d68f..b644e0124 100644 --- a/Podfile +++ b/Podfile @@ -16,9 +16,6 @@ abstract_target "App" do target "Core" do project './Core/Core.xcodeproj' workspace './Core/Core.xcodeproj' - #Firebase -# pod 'FirebaseAnalytics', '~> 10.11' -# pod 'FirebaseCrashlytics', '~> 10.11' #Networking pod 'Alamofire', '~> 5.7' #Keychain From c48416cd3baa2e60cabc3b066e3b89551ef21dbe Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Fri, 16 Feb 2024 14:02:46 +0100 Subject: [PATCH 10/21] chore: renamed google analytics to firebase analitics. added checking if firebase enabled --- OpenEdX.xcodeproj/project.pbxproj | 8 ++++---- OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift | 6 ++++-- ...lyticsService.swift => FirebaseAnalyticsService.swift} | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) rename OpenEdX/Managers/AnalyticsManager/Services/{GoogleAnalyticsService.swift => FirebaseAnalyticsService.swift} (88%) diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index ad68ce675..616ad4b81 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -53,7 +53,7 @@ A51CDBE52B6D1E93009B6D4E /* Segment in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBE42B6D1E93009B6D4E /* Segment */; }; A51CDBED2B6D2BEE009B6D4E /* SegmentBraze in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBEC2B6D2BEE009B6D4E /* SegmentBraze */; }; A51CDBEF2B6D2BEE009B6D4E /* SegmentBrazeUI in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBEE2B6D2BEE009B6D4E /* SegmentBrazeUI */; }; - A52508082B7E4FCC0078E1D8 /* GoogleAnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52508072B7E4FCC0078E1D8 /* GoogleAnalyticsService.swift */; }; + A52508082B7E4FCC0078E1D8 /* FirebaseAnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52508072B7E4FCC0078E1D8 /* FirebaseAnalyticsService.swift */; }; A525080A2B7E500A0078E1D8 /* SegmentAnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52508092B7E500A0078E1D8 /* SegmentAnalyticsService.swift */; }; A59568952B61630500ED4F90 /* DeepLinkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568942B61630500ED4F90 /* DeepLinkManager.swift */; }; A59568972B61653700ED4F90 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568962B61653700ED4F90 /* DeepLink.swift */; }; @@ -134,7 +134,7 @@ A50066902B61467B0024680B /* BrazeProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrazeProvider.swift; sourceTree = ""; }; A50066922B614DCD0024680B /* FCMListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCMListener.swift; sourceTree = ""; }; A50066942B614DEF0024680B /* BrazeListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrazeListener.swift; sourceTree = ""; }; - A52508072B7E4FCC0078E1D8 /* GoogleAnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleAnalyticsService.swift; sourceTree = ""; }; + A52508072B7E4FCC0078E1D8 /* FirebaseAnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseAnalyticsService.swift; sourceTree = ""; }; A52508092B7E500A0078E1D8 /* SegmentAnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentAnalyticsService.swift; sourceTree = ""; }; A59568942B61630500ED4F90 /* DeepLinkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkManager.swift; sourceTree = ""; }; A59568962B61653700ED4F90 /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = ""; }; @@ -326,7 +326,7 @@ A52508062B7E4FA90078E1D8 /* Services */ = { isa = PBXGroup; children = ( - A52508072B7E4FCC0078E1D8 /* GoogleAnalyticsService.swift */, + A52508072B7E4FCC0078E1D8 /* FirebaseAnalyticsService.swift */, A52508092B7E500A0078E1D8 /* SegmentAnalyticsService.swift */, ); path = Services; @@ -540,7 +540,7 @@ 0298DF302A4EF7230023A257 /* AnalyticsManager.swift in Sources */, A59585AF2B62A07100A35A20 /* BranchService.swift in Sources */, A525080A2B7E500A0078E1D8 /* SegmentAnalyticsService.swift in Sources */, - A52508082B7E4FCC0078E1D8 /* GoogleAnalyticsService.swift in Sources */, + A52508082B7E4FCC0078E1D8 /* FirebaseAnalyticsService.swift in Sources */, A50066912B61467B0024680B /* BrazeProvider.swift in Sources */, 0293A2072A6FCDA30090A336 /* DiscoveryPersistence.swift in Sources */, 07D5DA3528D075AA00752FD9 /* AppDelegate.swift in Sources */, diff --git a/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift b/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift index 16c0500ed..e4354d18a 100644 --- a/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift +++ b/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift @@ -36,8 +36,10 @@ class AnalyticsManager: AuthorizationAnalytics, private func servicesFor(config: ConfigProtocol) -> [AnalyticsService] { var analyticsServices: [AnalyticsService] = [] - // add Google Analytics Service - analyticsServices.append(GoogleAnalyticsService()) + // add Firebase Analytics Service if enabled and have config + if config.firebase.firebaseOptions != nil { + analyticsServices.append(FirebaseAnalyticsService()) + } // add Segment Analytics Service if enabled if config.segment.enabled { analyticsServices.append(SegmentAnalyticsService()) diff --git a/OpenEdX/Managers/AnalyticsManager/Services/GoogleAnalyticsService.swift b/OpenEdX/Managers/AnalyticsManager/Services/FirebaseAnalyticsService.swift similarity index 88% rename from OpenEdX/Managers/AnalyticsManager/Services/GoogleAnalyticsService.swift rename to OpenEdX/Managers/AnalyticsManager/Services/FirebaseAnalyticsService.swift index 608b83ad3..271591360 100644 --- a/OpenEdX/Managers/AnalyticsManager/Services/GoogleAnalyticsService.swift +++ b/OpenEdX/Managers/AnalyticsManager/Services/FirebaseAnalyticsService.swift @@ -8,7 +8,7 @@ import Foundation import FirebaseAnalytics -class GoogleAnalyticsService: AnalyticsService { +class FirebaseAnalyticsService: AnalyticsService { func identify(id: String, username: String?, email: String?) { Analytics.setUserID(id) } From 59b5be7d209b41c3a3e8aeff728482e2a417b46c Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Mon, 19 Feb 2024 18:53:20 +0100 Subject: [PATCH 11/21] chore: added FirebaseManager to setup standalone Firebase in separate file --- Core/Core.xcodeproj/project.pbxproj | 25 ---------------- .../Configuration/Config/FirebaseConfig.swift | 21 -------------- .../DiscussionTopicsViewModel.swift | 1 - OpenEdX.xcodeproj/project.pbxproj | 29 +++++++++++++++++++ OpenEdX/AppDelegate.swift | 12 ++++---- OpenEdX/Info.plist | 2 ++ .../AnalyticsManager/AnalyticsManager.swift | 6 ++-- .../FirebaseManager/FirebaseManager.swift | 15 ++++++++++ Podfile.lock | 2 +- 9 files changed, 56 insertions(+), 57 deletions(-) create mode 100644 OpenEdX/Managers/FirebaseManager/FirebaseManager.swift diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index ac3e61e30..c0c92ac6d 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -126,8 +126,6 @@ 07DDFCBD29A780BB00572595 /* UINavigationController+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07DDFCBC29A780BB00572595 /* UINavigationController+Animation.swift */; }; 07E0939F2B308D2800F1E4B2 /* Data_Certificate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07E0939E2B308D2800F1E4B2 /* Data_Certificate.swift */; }; 141F1D302B7328D4009E81EB /* WebviewCookiesUpdateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 141F1D2F2B7328D4009E81EB /* WebviewCookiesUpdateProtocol.swift */; }; - A51188632B729647004E9F8E /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = A51188622B729647004E9F8E /* FirebaseAnalytics */; }; - A51188652B72964F004E9F8E /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = A51188642B72964F004E9F8E /* FirebaseCrashlytics */; }; A51CDBE72B6D21F2009B6D4E /* SegmentConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51CDBE62B6D21F2009B6D4E /* SegmentConfig.swift */; }; A53A32352B233DEC005FE38A /* ThemeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A32342B233DEC005FE38A /* ThemeConfig.swift */; }; A595689B2B6173DF00ED4F90 /* BranchConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A595689A2B6173DF00ED4F90 /* BranchConfig.swift */; }; @@ -358,8 +356,6 @@ files = ( BAF0D4CB2AD6AE14007AC334 /* FacebookLogin in Frameworks */, 025EF2F62971740000B838AB /* YouTubePlayerKit in Frameworks */, - A51188632B729647004E9F8E /* FirebaseAnalytics in Frameworks */, - A51188652B72964F004E9F8E /* FirebaseCrashlytics in Frameworks */, C8C446EF233F81B9FABB77D2 /* Pods_App_Core.framework in Frameworks */, BA8FA66C2AD59BBC00EA029A /* GoogleSignIn in Frameworks */, E055A5392B18DC95008D9E5E /* Theme.framework in Frameworks */, @@ -877,8 +873,6 @@ 025EF2F52971740000B838AB /* YouTubePlayerKit */, BA8FA66B2AD59BBC00EA029A /* GoogleSignIn */, BAF0D4CA2AD6AE14007AC334 /* FacebookLogin */, - A51188622B729647004E9F8E /* FirebaseAnalytics */, - A51188642B72964F004E9F8E /* FirebaseCrashlytics */, ); productName = Core; productReference = 0770DE0828D07831006D8A5D /* Core.framework */; @@ -918,7 +912,6 @@ 025EF2F42971740000B838AB /* XCRemoteSwiftPackageReference "YouTubePlayerKit" */, BA8FA65E2AD574D700EA029A /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */, BA8FA6712AD6ABA300EA029A /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */, - A51188612B7295F7004E9F8E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, ); productRefGroup = 0770DE0928D07831006D8A5D /* Products */; projectDirPath = ""; @@ -2175,14 +2168,6 @@ minimumVersion = 1.5.0; }; }; - A51188612B7295F7004E9F8E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 10.20.0; - }; - }; BA8FA65E2AD574D700EA029A /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/google/GoogleSignIn-iOS"; @@ -2207,16 +2192,6 @@ package = 025EF2F42971740000B838AB /* XCRemoteSwiftPackageReference "YouTubePlayerKit" */; productName = YouTubePlayerKit; }; - A51188622B729647004E9F8E /* FirebaseAnalytics */ = { - isa = XCSwiftPackageProductDependency; - package = A51188612B7295F7004E9F8E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; - productName = FirebaseAnalytics; - }; - A51188642B72964F004E9F8E /* FirebaseCrashlytics */ = { - isa = XCSwiftPackageProductDependency; - package = A51188612B7295F7004E9F8E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; - productName = FirebaseCrashlytics; - }; BA8FA66B2AD59BBC00EA029A /* GoogleSignIn */ = { isa = XCSwiftPackageProductDependency; package = BA8FA65E2AD574D700EA029A /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */; diff --git a/Core/Core/Configuration/Config/FirebaseConfig.swift b/Core/Core/Configuration/Config/FirebaseConfig.swift index e8180f574..d70de04ab 100644 --- a/Core/Core/Configuration/Config/FirebaseConfig.swift +++ b/Core/Core/Configuration/Config/FirebaseConfig.swift @@ -6,7 +6,6 @@ // import Foundation -import FirebaseCore private enum FirebaseKeys: String { case enabled = "ENABLED" @@ -76,26 +75,6 @@ public final class FirebaseConfig: NSObject { public var isAnalyticsSourceFirebase: Bool { return analyticsSource == AnalyticsSource.firebase } - - public var firebaseOptions: FirebaseOptions? { - if enabled, - requiredKeysAvailable, - let bundleID = bundleID, - let googleAppID = googleAppID, - let gcmSenderID = gcmSenderID { - let firebaseOptions = FirebaseOptions(googleAppID: googleAppID, - gcmSenderID: gcmSenderID) - firebaseOptions.apiKey = apiKey - firebaseOptions.projectID = projectID - firebaseOptions.bundleID = bundleID - firebaseOptions.clientID = clientID - firebaseOptions.storageBucket = storageBucket - firebaseOptions.databaseURL = databaseURL - return firebaseOptions - } - - return nil - } } private let firebaseKey = "FIREBASE" diff --git a/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionTopicsViewModel.swift b/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionTopicsViewModel.swift index 85ec229e8..b14271f3e 100644 --- a/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionTopicsViewModel.swift +++ b/Discussion/Discussion/Presentation/DiscussionTopics/DiscussionTopicsViewModel.swift @@ -8,7 +8,6 @@ import Foundation import SwiftUI import Core -import FirebaseCrashlytics public class DiscussionTopicsViewModel: ObservableObject { diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index 616ad4b81..e0e109bcb 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -59,6 +59,8 @@ A59568972B61653700ED4F90 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568962B61653700ED4F90 /* DeepLink.swift */; }; A59568992B616D9400ED4F90 /* PushLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568982B616D9400ED4F90 /* PushLink.swift */; }; A59585AF2B62A07100A35A20 /* BranchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59585AE2B62A07100A35A20 /* BranchService.swift */; }; + A59702292B83C87900CA064C /* FirebaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59702282B83C87900CA064C /* FirebaseManager.swift */; }; + A5BD3E302B83B0F7006A8983 /* SegmentFirebase in Frameworks */ = {isa = PBXBuildFile; productRef = A5BD3E2F2B83B0F7006A8983 /* SegmentFirebase */; }; BA3042792B1F7147009B64B7 /* MSAL in Frameworks */ = {isa = PBXBuildFile; productRef = BA3042782B1F7147009B64B7 /* MSAL */; }; E0D6E6A32B1626B10089F9C9 /* Theme.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0D6E6A22B1626B10089F9C9 /* Theme.framework */; }; E0D6E6A42B1626D60089F9C9 /* Theme.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E0D6E6A22B1626B10089F9C9 /* Theme.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -140,6 +142,7 @@ A59568962B61653700ED4F90 /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = ""; }; A59568982B616D9400ED4F90 /* PushLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushLink.swift; sourceTree = ""; }; A59585AE2B62A07100A35A20 /* BranchService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BranchService.swift; path = ../BranchService.swift; sourceTree = ""; }; + A59702282B83C87900CA064C /* FirebaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseManager.swift; sourceTree = ""; }; A89AD827F52CF6A6B903606E /* Pods-App-OpenEdX.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-OpenEdX.releasestage.xcconfig"; path = "Target Support Files/Pods-App-OpenEdX/Pods-App-OpenEdX.releasestage.xcconfig"; sourceTree = ""; }; BB08ACD2CCA33D6DDDDD31B4 /* Pods-App-OpenEdX.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-OpenEdX.releasedev.xcconfig"; path = "Target Support Files/Pods-App-OpenEdX/Pods-App-OpenEdX.releasedev.xcconfig"; sourceTree = ""; }; E0D6E6A22B1626B10089F9C9 /* Theme.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Theme.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -162,6 +165,7 @@ 0218196428F734FA00202564 /* Discussion.framework in Frameworks */, 0219C67728F4347600D64452 /* Course.framework in Frameworks */, 027DB33028D8A063002B6862 /* Dashboard.framework in Frameworks */, + A5BD3E302B83B0F7006A8983 /* SegmentFirebase in Frameworks */, 95C140F3BDF778364986E83B /* Pods_App_OpenEdX.framework in Frameworks */, A51CDBED2B6D2BEE009B6D4E /* SegmentBraze in Frameworks */, A51CDBEF2B6D2BEE009B6D4E /* SegmentBrazeUI in Frameworks */, @@ -291,6 +295,7 @@ A59568932B6162E400ED4F90 /* DeepLinkManager */, A50066872B613E4B0024680B /* PushNotificationsManager */, A50066892B613E990024680B /* AnalyticsManager */, + A59702272B83C84800CA064C /* FirebaseManager */, ); path = Managers; sourceTree = ""; @@ -351,6 +356,14 @@ path = Link; sourceTree = ""; }; + A59702272B83C84800CA064C /* FirebaseManager */ = { + isa = PBXGroup; + children = ( + A59702282B83C87900CA064C /* FirebaseManager.swift */, + ); + path = FirebaseManager; + sourceTree = ""; + }; A5F46FD02B692B140003EEEF /* Services */ = { isa = PBXGroup; children = ( @@ -385,6 +398,7 @@ A51CDBE42B6D1E93009B6D4E /* Segment */, A51CDBEC2B6D2BEE009B6D4E /* SegmentBraze */, A51CDBEE2B6D2BEE009B6D4E /* SegmentBrazeUI */, + A5BD3E2F2B83B0F7006A8983 /* SegmentFirebase */, ); productName = OpenEdX; productReference = 07D5DA3128D075AA00752FD9 /* OpenEdX.app */; @@ -419,6 +433,7 @@ BA3042772B1F7147009B64B7 /* XCRemoteSwiftPackageReference "microsoft-authentication-library-for-objc" */, A51CDBE32B6D1E93009B6D4E /* XCRemoteSwiftPackageReference "analytics-swift" */, A51CDBEB2B6D2BEE009B6D4E /* XCRemoteSwiftPackageReference "braze-segment-swift" */, + A5BD3E2E2B83B0F7006A8983 /* XCRemoteSwiftPackageReference "analytics-swift-firebase" */, ); productRefGroup = 07D5DA3228D075AA00752FD9 /* Products */; projectDirPath = ""; @@ -550,6 +565,7 @@ 0293A2032A6FCA590090A336 /* CorePersistence.swift in Sources */, 0770DE1E28D084E8006D8A5D /* AppAssembly.swift in Sources */, A50066932B614DCD0024680B /* FCMListener.swift in Sources */, + A59702292B83C87900CA064C /* FirebaseManager.swift in Sources */, A500668D2B6143000024680B /* FCMProvider.swift in Sources */, 025AD4AC2A6FB95C00AB8FA7 /* DatabaseManager.swift in Sources */, 024E69202AEFC3FB00FA0B59 /* MainScreenViewModel.swift in Sources */, @@ -1199,6 +1215,14 @@ minimumVersion = 2.2.0; }; }; + A5BD3E2E2B83B0F7006A8983 /* XCRemoteSwiftPackageReference "analytics-swift-firebase" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/segment-integrations/analytics-swift-firebase"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.3.5; + }; + }; BA3042772B1F7147009B64B7 /* XCRemoteSwiftPackageReference "microsoft-authentication-library-for-objc" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/AzureAD/microsoft-authentication-library-for-objc"; @@ -1225,6 +1249,11 @@ package = A51CDBEB2B6D2BEE009B6D4E /* XCRemoteSwiftPackageReference "braze-segment-swift" */; productName = SegmentBrazeUI; }; + A5BD3E2F2B83B0F7006A8983 /* SegmentFirebase */ = { + isa = XCSwiftPackageProductDependency; + package = A5BD3E2E2B83B0F7006A8983 /* XCRemoteSwiftPackageReference "analytics-swift-firebase" */; + productName = SegmentFirebase; + }; BA3042782B1F7147009B64B7 /* MSAL */ = { isa = XCSwiftPackageProductDependency; package = BA3042772B1F7147009B64B7 /* XCRemoteSwiftPackageReference "microsoft-authentication-library-for-objc" */; diff --git a/OpenEdX/AppDelegate.swift b/OpenEdX/AppDelegate.swift index 6f7ee65f4..e6c45d825 100644 --- a/OpenEdX/AppDelegate.swift +++ b/OpenEdX/AppDelegate.swift @@ -8,14 +8,13 @@ import UIKit import Core import Swinject -import FirebaseCore -import FirebaseCrashlytics import Profile import GoogleSignIn import FacebookCore import MSAL import Theme import Segment +import SegmentFirebase @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -37,12 +36,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { initDI() - if let config = Container.shared.resolve(ConfigProtocol.self) { Theme.Shapes.isRoundedCorners = config.theme.isRoundedCorners - if let configuration = config.firebase.firebaseOptions { - FirebaseApp.configure(options: configuration) - Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(true) + if config.firebase.isAnalyticsSourceFirebase { + FirebaseManager.setup() } if config.facebook.enabled { ApplicationDelegate.shared.application( @@ -56,6 +53,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { .trackApplicationLifecycleEvents(true) .flushInterval(10) analytics = Analytics(configuration: configuration) + if config.firebase.isAnalyticsSourceSegment { + analytics?.add(plugin: FirebaseDestination()) + } } } diff --git a/OpenEdX/Info.plist b/OpenEdX/Info.plist index b74f3967c..dfb2fae04 100644 --- a/OpenEdX/Info.plist +++ b/OpenEdX/Info.plist @@ -29,5 +29,7 @@ UIViewControllerBasedStatusBarAppearance + FirebaseCrashlyticsCollectionEnabled + diff --git a/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift b/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift index e4354d18a..7756c3b7d 100644 --- a/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift +++ b/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift @@ -36,12 +36,12 @@ class AnalyticsManager: AuthorizationAnalytics, private func servicesFor(config: ConfigProtocol) -> [AnalyticsService] { var analyticsServices: [AnalyticsService] = [] - // add Firebase Analytics Service if enabled and have config - if config.firebase.firebaseOptions != nil { + // add Firebase Analytics Service if enabled + if config.firebase.isAnalyticsSourceFirebase { analyticsServices.append(FirebaseAnalyticsService()) } // add Segment Analytics Service if enabled - if config.segment.enabled { + if config.firebase.isAnalyticsSourceSegment { analyticsServices.append(SegmentAnalyticsService()) } return analyticsServices diff --git a/OpenEdX/Managers/FirebaseManager/FirebaseManager.swift b/OpenEdX/Managers/FirebaseManager/FirebaseManager.swift new file mode 100644 index 000000000..4f5c4cf2b --- /dev/null +++ b/OpenEdX/Managers/FirebaseManager/FirebaseManager.swift @@ -0,0 +1,15 @@ +// +// FirebaseManager.swift +// OpenEdX +// +// Created by Anton Yarmolenka on 19/02/2024. +// + +import Foundation +import Firebase + +class FirebaseManager { + class func setup() { + FirebaseApp.configure() + } +} diff --git a/Podfile.lock b/Podfile.lock index efae391c2..f3f760cf7 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -54,6 +54,6 @@ SPEC CHECKSUMS: SwiftyMocky: c5e96e4ff76ec6dbf5a5941aeb039b5a546954a0 Swinject: 893c9a543000ac2f10ee4cbaf0933c6992c935d5 -PODFILE CHECKSUM: b4e49ed566507f9bbf9bcff18accdca28f28e106 +PODFILE CHECKSUM: 881176d00eabfe8f78d6022c56c277cf61aad22b COCOAPODS: 1.15.0 From 78a4f3d4bf04ef0ee2364fd2286c515cf412c223 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Wed, 21 Feb 2024 13:17:01 +0100 Subject: [PATCH 12/21] chore: moved analytics segment var into SegmentManager --- OpenEdX.xcodeproj/project.pbxproj | 12 +++++++++ OpenEdX/AppDelegate.swift | 18 ++++++------- OpenEdX/DI/AppAssembly.swift | 4 +++ .../Services/SegmentAnalyticsService.swift | 11 +++++--- .../Providers/BrazeProvider.swift | 4 ++- .../SegmentManager/SegmentManager.swift | 25 +++++++++++++++++++ 6 files changed, 59 insertions(+), 15 deletions(-) create mode 100644 OpenEdX/Managers/SegmentManager/SegmentManager.swift diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index e0e109bcb..e69934396 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -61,6 +61,7 @@ A59585AF2B62A07100A35A20 /* BranchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59585AE2B62A07100A35A20 /* BranchService.swift */; }; A59702292B83C87900CA064C /* FirebaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59702282B83C87900CA064C /* FirebaseManager.swift */; }; A5BD3E302B83B0F7006A8983 /* SegmentFirebase in Frameworks */ = {isa = PBXBuildFile; productRef = A5BD3E2F2B83B0F7006A8983 /* SegmentFirebase */; }; + A5C10D8F2B861A70008E864D /* SegmentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5C10D8E2B861A70008E864D /* SegmentManager.swift */; }; BA3042792B1F7147009B64B7 /* MSAL in Frameworks */ = {isa = PBXBuildFile; productRef = BA3042782B1F7147009B64B7 /* MSAL */; }; E0D6E6A32B1626B10089F9C9 /* Theme.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0D6E6A22B1626B10089F9C9 /* Theme.framework */; }; E0D6E6A42B1626D60089F9C9 /* Theme.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E0D6E6A22B1626B10089F9C9 /* Theme.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -143,6 +144,7 @@ A59568982B616D9400ED4F90 /* PushLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushLink.swift; sourceTree = ""; }; A59585AE2B62A07100A35A20 /* BranchService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BranchService.swift; path = ../BranchService.swift; sourceTree = ""; }; A59702282B83C87900CA064C /* FirebaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseManager.swift; sourceTree = ""; }; + A5C10D8E2B861A70008E864D /* SegmentManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentManager.swift; sourceTree = ""; }; A89AD827F52CF6A6B903606E /* Pods-App-OpenEdX.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-OpenEdX.releasestage.xcconfig"; path = "Target Support Files/Pods-App-OpenEdX/Pods-App-OpenEdX.releasestage.xcconfig"; sourceTree = ""; }; BB08ACD2CCA33D6DDDDD31B4 /* Pods-App-OpenEdX.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-OpenEdX.releasedev.xcconfig"; path = "Target Support Files/Pods-App-OpenEdX/Pods-App-OpenEdX.releasedev.xcconfig"; sourceTree = ""; }; E0D6E6A22B1626B10089F9C9 /* Theme.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Theme.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -296,6 +298,7 @@ A50066872B613E4B0024680B /* PushNotificationsManager */, A50066892B613E990024680B /* AnalyticsManager */, A59702272B83C84800CA064C /* FirebaseManager */, + A5C10D8D2B861A56008E864D /* SegmentManager */, ); path = Managers; sourceTree = ""; @@ -364,6 +367,14 @@ path = FirebaseManager; sourceTree = ""; }; + A5C10D8D2B861A56008E864D /* SegmentManager */ = { + isa = PBXGroup; + children = ( + A5C10D8E2B861A70008E864D /* SegmentManager.swift */, + ); + path = SegmentManager; + sourceTree = ""; + }; A5F46FD02B692B140003EEEF /* Services */ = { isa = PBXGroup; children = ( @@ -576,6 +587,7 @@ A50066952B614DEF0024680B /* BrazeListener.swift in Sources */, 071009C928D1DB3F00344290 /* ScreenAssembly.swift in Sources */, A59568972B61653700ED4F90 /* DeepLink.swift in Sources */, + A5C10D8F2B861A70008E864D /* SegmentManager.swift in Sources */, A59568992B616D9400ED4F90 /* PushLink.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/OpenEdX/AppDelegate.swift b/OpenEdX/AppDelegate.swift index e6c45d825..965d81b41 100644 --- a/OpenEdX/AppDelegate.swift +++ b/OpenEdX/AppDelegate.swift @@ -13,8 +13,6 @@ import GoogleSignIn import FacebookCore import MSAL import Theme -import Segment -import SegmentFirebase @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -22,8 +20,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { static var shared: AppDelegate { UIApplication.shared.delegate as! AppDelegate } - - var analytics: Analytics? var window: UIWindow? @@ -49,13 +45,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } configureDeepLinkServices(launchOptions: launchOptions) if config.segment.enabled { - let configuration = Configuration(writeKey: config.segment.writeKey) - .trackApplicationLifecycleEvents(true) - .flushInterval(10) - analytics = Analytics(configuration: configuration) - if config.firebase.isAnalyticsSourceSegment { - analytics?.add(plugin: FirebaseDestination()) - } + configureSegment(config) } } @@ -179,4 +169,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { guard let deepLinkManager = Container.shared.resolve(DeepLinkManager.self) else { return } deepLinkManager.configureDeepLinkService(launchOptions: launchOptions) } + + // Segment + func configureSegment(_ config: ConfigProtocol) { + guard let segmentManager = Container.shared.resolve(SegmentManager.self) else { return } + segmentManager.setup(with: config) + } } diff --git a/OpenEdX/DI/AppAssembly.swift b/OpenEdX/DI/AppAssembly.swift index e2ebd1479..850bf8b17 100644 --- a/OpenEdX/DI/AppAssembly.swift +++ b/OpenEdX/DI/AppAssembly.swift @@ -175,6 +175,10 @@ class AppAssembly: Assembly { config: r.resolve(ConfigProtocol.self)! ) }.inObjectScope(.container) + + container.register(SegmentManager.self) { r in + SegmentManager() + }.inObjectScope(.container) } } // swiftlint:enable function_body_length diff --git a/OpenEdX/Managers/AnalyticsManager/Services/SegmentAnalyticsService.swift b/OpenEdX/Managers/AnalyticsManager/Services/SegmentAnalyticsService.swift index 5515e5bc6..764d675fb 100644 --- a/OpenEdX/Managers/AnalyticsManager/Services/SegmentAnalyticsService.swift +++ b/OpenEdX/Managers/AnalyticsManager/Services/SegmentAnalyticsService.swift @@ -7,18 +7,23 @@ import Foundation import UIKit +import Swinject class SegmentAnalyticsService: AnalyticsService { func identify(id: String, username: String?, email: String?) { - guard let email = email, let username = username else { return } + guard let email = email, + let username = username, + let segmentManager = Container.shared.resolve(SegmentManager.self) + else { return } let traits: [String: String] = [ "email": email, "username": username ] - (UIApplication.shared.delegate as? AppDelegate)?.analytics?.identify(userId: id, traits: traits) + segmentManager.analytics?.identify(userId: id, traits: traits) } func logEvent(_ event: Event, parameters: [String: Any]?) { - (UIApplication.shared.delegate as? AppDelegate)?.analytics?.track( + guard let segmentManager = Container.shared.resolve(SegmentManager.self) else { return } + segmentManager.analytics?.track( name: event.rawValue, properties: parameters ) diff --git a/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift b/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift index b086af806..dfb32a3e2 100644 --- a/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift +++ b/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift @@ -8,10 +8,12 @@ import Foundation import UIKit import SegmentBrazeUI +import Swinject class BrazeProvider: PushNotificationsProvider { func didRegisterWithDeviceToken(deviceToken: Data) { - (UIApplication.shared.delegate as? AppDelegate)?.analytics?.add( + guard let segmentManager = Container.shared.resolve(SegmentManager.self) else { return } + segmentManager.analytics?.add( plugin: BrazeDestination( additionalConfiguration: { configuration in configuration.logger.level = .debug diff --git a/OpenEdX/Managers/SegmentManager/SegmentManager.swift b/OpenEdX/Managers/SegmentManager/SegmentManager.swift new file mode 100644 index 000000000..70daa50c8 --- /dev/null +++ b/OpenEdX/Managers/SegmentManager/SegmentManager.swift @@ -0,0 +1,25 @@ +// +// SegmentManager.swift +// OpenEdX +// +// Created by Anton Yarmolenka on 21/02/2024. +// + +import Foundation +import Core +import Segment +import SegmentFirebase + +class SegmentManager { + var analytics: Analytics? + + public func setup(with config: ConfigProtocol) { + let configuration = Configuration(writeKey: config.segment.writeKey) + .trackApplicationLifecycleEvents(true) + .flushInterval(10) + analytics = Analytics(configuration: configuration) + if config.firebase.isAnalyticsSourceSegment { + analytics?.add(plugin: FirebaseDestination()) + } + } +} From 81984f916a5fc1cd10c5bde26727587584347990 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Wed, 21 Feb 2024 13:32:50 +0100 Subject: [PATCH 13/21] chore: moved AnalyticsService protocol implementation into Firebase and Segment managers --- OpenEdX.xcodeproj/project.pbxproj | 16 ---------- .../AnalyticsManager/AnalyticsManager.swift | 8 +++-- .../Services/FirebaseAnalyticsService.swift | 18 ----------- .../Services/SegmentAnalyticsService.swift | 31 ------------------- .../FirebaseManager/FirebaseManager.swift | 11 ++++++- .../SegmentManager/SegmentManager.swift | 18 ++++++++++- 6 files changed, 32 insertions(+), 70 deletions(-) delete mode 100644 OpenEdX/Managers/AnalyticsManager/Services/FirebaseAnalyticsService.swift delete mode 100644 OpenEdX/Managers/AnalyticsManager/Services/SegmentAnalyticsService.swift diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index e69934396..5f4a3c339 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -53,8 +53,6 @@ A51CDBE52B6D1E93009B6D4E /* Segment in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBE42B6D1E93009B6D4E /* Segment */; }; A51CDBED2B6D2BEE009B6D4E /* SegmentBraze in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBEC2B6D2BEE009B6D4E /* SegmentBraze */; }; A51CDBEF2B6D2BEE009B6D4E /* SegmentBrazeUI in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBEE2B6D2BEE009B6D4E /* SegmentBrazeUI */; }; - A52508082B7E4FCC0078E1D8 /* FirebaseAnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52508072B7E4FCC0078E1D8 /* FirebaseAnalyticsService.swift */; }; - A525080A2B7E500A0078E1D8 /* SegmentAnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52508092B7E500A0078E1D8 /* SegmentAnalyticsService.swift */; }; A59568952B61630500ED4F90 /* DeepLinkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568942B61630500ED4F90 /* DeepLinkManager.swift */; }; A59568972B61653700ED4F90 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568962B61653700ED4F90 /* DeepLink.swift */; }; A59568992B616D9400ED4F90 /* PushLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568982B616D9400ED4F90 /* PushLink.swift */; }; @@ -137,8 +135,6 @@ A50066902B61467B0024680B /* BrazeProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrazeProvider.swift; sourceTree = ""; }; A50066922B614DCD0024680B /* FCMListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCMListener.swift; sourceTree = ""; }; A50066942B614DEF0024680B /* BrazeListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrazeListener.swift; sourceTree = ""; }; - A52508072B7E4FCC0078E1D8 /* FirebaseAnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseAnalyticsService.swift; sourceTree = ""; }; - A52508092B7E500A0078E1D8 /* SegmentAnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentAnalyticsService.swift; sourceTree = ""; }; A59568942B61630500ED4F90 /* DeepLinkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkManager.swift; sourceTree = ""; }; A59568962B61653700ED4F90 /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = ""; }; A59568982B616D9400ED4F90 /* PushLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushLink.swift; sourceTree = ""; }; @@ -307,7 +303,6 @@ isa = PBXGroup; children = ( 0298DF2F2A4EF7230023A257 /* AnalyticsManager.swift */, - A52508062B7E4FA90078E1D8 /* Services */, 02F175302A4DA95B0019CD70 /* MainScreenAnalytics.swift */, ); path = AnalyticsManager; @@ -331,15 +326,6 @@ path = Listeners; sourceTree = ""; }; - A52508062B7E4FA90078E1D8 /* Services */ = { - isa = PBXGroup; - children = ( - A52508072B7E4FCC0078E1D8 /* FirebaseAnalyticsService.swift */, - A52508092B7E500A0078E1D8 /* SegmentAnalyticsService.swift */, - ); - path = Services; - sourceTree = ""; - }; A59568932B6162E400ED4F90 /* DeepLinkManager */ = { isa = PBXGroup; children = ( @@ -565,8 +551,6 @@ 020CA5D92AA0A25300970AAF /* AppStorage.swift in Sources */, 0298DF302A4EF7230023A257 /* AnalyticsManager.swift in Sources */, A59585AF2B62A07100A35A20 /* BranchService.swift in Sources */, - A525080A2B7E500A0078E1D8 /* SegmentAnalyticsService.swift in Sources */, - A52508082B7E4FCC0078E1D8 /* FirebaseAnalyticsService.swift in Sources */, A50066912B61467B0024680B /* BrazeProvider.swift in Sources */, 0293A2072A6FCDA30090A336 /* DiscoveryPersistence.swift in Sources */, 07D5DA3528D075AA00752FD9 /* AppDelegate.swift in Sources */, diff --git a/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift b/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift index 7756c3b7d..0928c10ad 100644 --- a/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift +++ b/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift @@ -13,6 +13,7 @@ import Dashboard import Profile import Course import Discussion +import Swinject protocol AnalyticsService { func identify(id: String, username: String?, email: String?) @@ -38,11 +39,12 @@ class AnalyticsManager: AuthorizationAnalytics, var analyticsServices: [AnalyticsService] = [] // add Firebase Analytics Service if enabled if config.firebase.isAnalyticsSourceFirebase { - analyticsServices.append(FirebaseAnalyticsService()) + analyticsServices.append(FirebaseManager()) } // add Segment Analytics Service if enabled - if config.firebase.isAnalyticsSourceSegment { - analyticsServices.append(SegmentAnalyticsService()) + if config.firebase.isAnalyticsSourceSegment, + let segmentManager = Container.shared.resolve(SegmentManager.self) { + analyticsServices.append(segmentManager) } return analyticsServices } diff --git a/OpenEdX/Managers/AnalyticsManager/Services/FirebaseAnalyticsService.swift b/OpenEdX/Managers/AnalyticsManager/Services/FirebaseAnalyticsService.swift deleted file mode 100644 index 271591360..000000000 --- a/OpenEdX/Managers/AnalyticsManager/Services/FirebaseAnalyticsService.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// GoogleAnalyticsService.swift -// OpenEdX -// -// Created by Anton Yarmolenka on 15/02/2024. -// - -import Foundation -import FirebaseAnalytics - -class FirebaseAnalyticsService: AnalyticsService { - func identify(id: String, username: String?, email: String?) { - Analytics.setUserID(id) - } - func logEvent(_ event: Event, parameters: [String: Any]?) { - Analytics.logEvent(event.rawValue, parameters: parameters) - } -} diff --git a/OpenEdX/Managers/AnalyticsManager/Services/SegmentAnalyticsService.swift b/OpenEdX/Managers/AnalyticsManager/Services/SegmentAnalyticsService.swift deleted file mode 100644 index 764d675fb..000000000 --- a/OpenEdX/Managers/AnalyticsManager/Services/SegmentAnalyticsService.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// SegmentAnalyticsService.swift -// OpenEdX -// -// Created by Anton Yarmolenka on 15/02/2024. -// - -import Foundation -import UIKit -import Swinject - -class SegmentAnalyticsService: AnalyticsService { - func identify(id: String, username: String?, email: String?) { - guard let email = email, - let username = username, - let segmentManager = Container.shared.resolve(SegmentManager.self) - else { return } - let traits: [String: String] = [ - "email": email, - "username": username - ] - segmentManager.analytics?.identify(userId: id, traits: traits) - } - func logEvent(_ event: Event, parameters: [String: Any]?) { - guard let segmentManager = Container.shared.resolve(SegmentManager.self) else { return } - segmentManager.analytics?.track( - name: event.rawValue, - properties: parameters - ) - } -} diff --git a/OpenEdX/Managers/FirebaseManager/FirebaseManager.swift b/OpenEdX/Managers/FirebaseManager/FirebaseManager.swift index 4f5c4cf2b..2e44ef373 100644 --- a/OpenEdX/Managers/FirebaseManager/FirebaseManager.swift +++ b/OpenEdX/Managers/FirebaseManager/FirebaseManager.swift @@ -8,8 +8,17 @@ import Foundation import Firebase -class FirebaseManager { +class FirebaseManager: AnalyticsService { class func setup() { FirebaseApp.configure() } + + func identify(id: String, username: String?, email: String?) { + Analytics.setUserID(id) + } + + func logEvent(_ event: Event, parameters: [String: Any]?) { + Analytics.logEvent(event.rawValue, parameters: parameters) + } + } diff --git a/OpenEdX/Managers/SegmentManager/SegmentManager.swift b/OpenEdX/Managers/SegmentManager/SegmentManager.swift index 70daa50c8..c134f4cb1 100644 --- a/OpenEdX/Managers/SegmentManager/SegmentManager.swift +++ b/OpenEdX/Managers/SegmentManager/SegmentManager.swift @@ -10,7 +10,7 @@ import Core import Segment import SegmentFirebase -class SegmentManager { +class SegmentManager: AnalyticsService { var analytics: Analytics? public func setup(with config: ConfigProtocol) { @@ -22,4 +22,20 @@ class SegmentManager { analytics?.add(plugin: FirebaseDestination()) } } + + func identify(id: String, username: String?, email: String?) { + guard let email = email, let username = username else { return } + let traits: [String: String] = [ + "email": email, + "username": username + ] + analytics?.identify(userId: id, traits: traits) + } + + func logEvent(_ event: Event, parameters: [String: Any]?) { + analytics?.track( + name: event.rawValue, + properties: parameters + ) + } } From 70285cca29c2cab314b8eb2a7804899d4194b05f Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Wed, 21 Feb 2024 14:36:12 +0100 Subject: [PATCH 14/21] chore: moved setup into managers init methods, renamed managers --- OpenEdX.xcodeproj/project.pbxproj | 16 ++++++++-------- OpenEdX/AppDelegate.swift | 13 +------------ OpenEdX/DI/AppAssembly.swift | 10 ++++++++-- .../AnalyticsManager/AnalyticsManager.swift | 14 ++++++++------ ...ager.swift => FirebaseAnalyticsManager.swift} | 6 ++++-- .../Providers/BrazeProvider.swift | 3 ++- ...nager.swift => SegmentAnalyticsManager.swift} | 5 +++-- 7 files changed, 34 insertions(+), 33 deletions(-) rename OpenEdX/Managers/FirebaseManager/{FirebaseManager.swift => FirebaseAnalyticsManager.swift} (81%) rename OpenEdX/Managers/SegmentManager/{SegmentManager.swift => SegmentAnalyticsManager.swift} (90%) diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index 5f4a3c339..eee455ddb 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -57,9 +57,9 @@ A59568972B61653700ED4F90 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568962B61653700ED4F90 /* DeepLink.swift */; }; A59568992B616D9400ED4F90 /* PushLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568982B616D9400ED4F90 /* PushLink.swift */; }; A59585AF2B62A07100A35A20 /* BranchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59585AE2B62A07100A35A20 /* BranchService.swift */; }; - A59702292B83C87900CA064C /* FirebaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59702282B83C87900CA064C /* FirebaseManager.swift */; }; + A59702292B83C87900CA064C /* FirebaseAnalyticsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59702282B83C87900CA064C /* FirebaseAnalyticsManager.swift */; }; A5BD3E302B83B0F7006A8983 /* SegmentFirebase in Frameworks */ = {isa = PBXBuildFile; productRef = A5BD3E2F2B83B0F7006A8983 /* SegmentFirebase */; }; - A5C10D8F2B861A70008E864D /* SegmentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5C10D8E2B861A70008E864D /* SegmentManager.swift */; }; + A5C10D8F2B861A70008E864D /* SegmentAnalyticsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5C10D8E2B861A70008E864D /* SegmentAnalyticsManager.swift */; }; BA3042792B1F7147009B64B7 /* MSAL in Frameworks */ = {isa = PBXBuildFile; productRef = BA3042782B1F7147009B64B7 /* MSAL */; }; E0D6E6A32B1626B10089F9C9 /* Theme.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0D6E6A22B1626B10089F9C9 /* Theme.framework */; }; E0D6E6A42B1626D60089F9C9 /* Theme.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E0D6E6A22B1626B10089F9C9 /* Theme.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -139,8 +139,8 @@ A59568962B61653700ED4F90 /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = ""; }; A59568982B616D9400ED4F90 /* PushLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushLink.swift; sourceTree = ""; }; A59585AE2B62A07100A35A20 /* BranchService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BranchService.swift; path = ../BranchService.swift; sourceTree = ""; }; - A59702282B83C87900CA064C /* FirebaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseManager.swift; sourceTree = ""; }; - A5C10D8E2B861A70008E864D /* SegmentManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentManager.swift; sourceTree = ""; }; + A59702282B83C87900CA064C /* FirebaseAnalyticsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseAnalyticsManager.swift; sourceTree = ""; }; + A5C10D8E2B861A70008E864D /* SegmentAnalyticsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentAnalyticsManager.swift; sourceTree = ""; }; A89AD827F52CF6A6B903606E /* Pods-App-OpenEdX.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-OpenEdX.releasestage.xcconfig"; path = "Target Support Files/Pods-App-OpenEdX/Pods-App-OpenEdX.releasestage.xcconfig"; sourceTree = ""; }; BB08ACD2CCA33D6DDDDD31B4 /* Pods-App-OpenEdX.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-OpenEdX.releasedev.xcconfig"; path = "Target Support Files/Pods-App-OpenEdX/Pods-App-OpenEdX.releasedev.xcconfig"; sourceTree = ""; }; E0D6E6A22B1626B10089F9C9 /* Theme.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Theme.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -348,7 +348,7 @@ A59702272B83C84800CA064C /* FirebaseManager */ = { isa = PBXGroup; children = ( - A59702282B83C87900CA064C /* FirebaseManager.swift */, + A59702282B83C87900CA064C /* FirebaseAnalyticsManager.swift */, ); path = FirebaseManager; sourceTree = ""; @@ -356,7 +356,7 @@ A5C10D8D2B861A56008E864D /* SegmentManager */ = { isa = PBXGroup; children = ( - A5C10D8E2B861A70008E864D /* SegmentManager.swift */, + A5C10D8E2B861A70008E864D /* SegmentAnalyticsManager.swift */, ); path = SegmentManager; sourceTree = ""; @@ -560,7 +560,7 @@ 0293A2032A6FCA590090A336 /* CorePersistence.swift in Sources */, 0770DE1E28D084E8006D8A5D /* AppAssembly.swift in Sources */, A50066932B614DCD0024680B /* FCMListener.swift in Sources */, - A59702292B83C87900CA064C /* FirebaseManager.swift in Sources */, + A59702292B83C87900CA064C /* FirebaseAnalyticsManager.swift in Sources */, A500668D2B6143000024680B /* FCMProvider.swift in Sources */, 025AD4AC2A6FB95C00AB8FA7 /* DatabaseManager.swift in Sources */, 024E69202AEFC3FB00FA0B59 /* MainScreenViewModel.swift in Sources */, @@ -571,7 +571,7 @@ A50066952B614DEF0024680B /* BrazeListener.swift in Sources */, 071009C928D1DB3F00344290 /* ScreenAssembly.swift in Sources */, A59568972B61653700ED4F90 /* DeepLink.swift in Sources */, - A5C10D8F2B861A70008E864D /* SegmentManager.swift in Sources */, + A5C10D8F2B861A70008E864D /* SegmentAnalyticsManager.swift in Sources */, A59568992B616D9400ED4F90 /* PushLink.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/OpenEdX/AppDelegate.swift b/OpenEdX/AppDelegate.swift index 965d81b41..1f71d7209 100644 --- a/OpenEdX/AppDelegate.swift +++ b/OpenEdX/AppDelegate.swift @@ -34,9 +34,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { initDI() if let config = Container.shared.resolve(ConfigProtocol.self) { Theme.Shapes.isRoundedCorners = config.theme.isRoundedCorners - if config.firebase.isAnalyticsSourceFirebase { - FirebaseManager.setup() - } + if config.facebook.enabled { ApplicationDelegate.shared.application( application, @@ -44,9 +42,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { ) } configureDeepLinkServices(launchOptions: launchOptions) - if config.segment.enabled { - configureSegment(config) - } } Theme.Fonts.registerFonts() @@ -169,10 +164,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate { guard let deepLinkManager = Container.shared.resolve(DeepLinkManager.self) else { return } deepLinkManager.configureDeepLinkService(launchOptions: launchOptions) } - - // Segment - func configureSegment(_ config: ConfigProtocol) { - guard let segmentManager = Container.shared.resolve(SegmentManager.self) else { return } - segmentManager.setup(with: config) - } } diff --git a/OpenEdX/DI/AppAssembly.swift b/OpenEdX/DI/AppAssembly.swift index 850bf8b17..91a0ddbb8 100644 --- a/OpenEdX/DI/AppAssembly.swift +++ b/OpenEdX/DI/AppAssembly.swift @@ -176,8 +176,14 @@ class AppAssembly: Assembly { ) }.inObjectScope(.container) - container.register(SegmentManager.self) { r in - SegmentManager() + container.register(SegmentAnalyticsManager.self) { r in + SegmentAnalyticsManager( + config: r.resolve(ConfigProtocol.self)! + ) + }.inObjectScope(.container) + + container.register(FirebaseAnalyticsManager.self) { r in + FirebaseAnalyticsManager() }.inObjectScope(.container) } } diff --git a/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift b/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift index 0928c10ad..105605f64 100644 --- a/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift +++ b/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift @@ -37,13 +37,15 @@ class AnalyticsManager: AuthorizationAnalytics, private func servicesFor(config: ConfigProtocol) -> [AnalyticsService] { var analyticsServices: [AnalyticsService] = [] - // add Firebase Analytics Service if enabled - if config.firebase.isAnalyticsSourceFirebase { - analyticsServices.append(FirebaseManager()) + // add Firebase Analytics Service + if config.firebase.enabled && config.firebase.isAnalyticsSourceFirebase, + let firebaseManager = Container.shared.resolve(FirebaseAnalyticsManager.self) { + analyticsServices.append(firebaseManager) } - // add Segment Analytics Service if enabled - if config.firebase.isAnalyticsSourceSegment, - let segmentManager = Container.shared.resolve(SegmentManager.self) { + + // add Segment Analytics Service + if config.segment.enabled, + let segmentManager = Container.shared.resolve(SegmentAnalyticsManager.self) { analyticsServices.append(segmentManager) } return analyticsServices diff --git a/OpenEdX/Managers/FirebaseManager/FirebaseManager.swift b/OpenEdX/Managers/FirebaseManager/FirebaseAnalyticsManager.swift similarity index 81% rename from OpenEdX/Managers/FirebaseManager/FirebaseManager.swift rename to OpenEdX/Managers/FirebaseManager/FirebaseAnalyticsManager.swift index 2e44ef373..faf49a148 100644 --- a/OpenEdX/Managers/FirebaseManager/FirebaseManager.swift +++ b/OpenEdX/Managers/FirebaseManager/FirebaseAnalyticsManager.swift @@ -7,9 +7,11 @@ import Foundation import Firebase +import Core -class FirebaseManager: AnalyticsService { - class func setup() { +class FirebaseAnalyticsManager: AnalyticsService { + // Init manager + init() { FirebaseApp.configure() } diff --git a/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift b/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift index dfb32a3e2..7b21b21be 100644 --- a/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift +++ b/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift @@ -12,7 +12,7 @@ import Swinject class BrazeProvider: PushNotificationsProvider { func didRegisterWithDeviceToken(deviceToken: Data) { - guard let segmentManager = Container.shared.resolve(SegmentManager.self) else { return } + guard let segmentManager = Container.shared.resolve(SegmentAnalyticsManager.self) else { return } segmentManager.analytics?.add( plugin: BrazeDestination( additionalConfiguration: { configuration in @@ -23,6 +23,7 @@ class BrazeProvider: PushNotificationsProvider { ) ) } + func didFailToRegisterForRemoteNotificationsWithError(error: Error) { } } diff --git a/OpenEdX/Managers/SegmentManager/SegmentManager.swift b/OpenEdX/Managers/SegmentManager/SegmentAnalyticsManager.swift similarity index 90% rename from OpenEdX/Managers/SegmentManager/SegmentManager.swift rename to OpenEdX/Managers/SegmentManager/SegmentAnalyticsManager.swift index c134f4cb1..960492a13 100644 --- a/OpenEdX/Managers/SegmentManager/SegmentManager.swift +++ b/OpenEdX/Managers/SegmentManager/SegmentAnalyticsManager.swift @@ -10,10 +10,11 @@ import Core import Segment import SegmentFirebase -class SegmentManager: AnalyticsService { +class SegmentAnalyticsManager: AnalyticsService { var analytics: Analytics? - public func setup(with config: ConfigProtocol) { + // Init manager + public init(config: ConfigProtocol) { let configuration = Configuration(writeKey: config.segment.writeKey) .trackApplicationLifecycleEvents(true) .flushInterval(10) From 17b8ee77524018353c1da9c29c4a0844d792ac95 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Wed, 21 Feb 2024 15:03:44 +0100 Subject: [PATCH 15/21] chore: added check if services are enabled while init --- OpenEdX/DI/AppAssembly.swift | 4 +++- .../Managers/FirebaseManager/FirebaseAnalyticsManager.swift | 4 +++- OpenEdX/Managers/SegmentManager/SegmentAnalyticsManager.swift | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/OpenEdX/DI/AppAssembly.swift b/OpenEdX/DI/AppAssembly.swift index 91a0ddbb8..ac404b136 100644 --- a/OpenEdX/DI/AppAssembly.swift +++ b/OpenEdX/DI/AppAssembly.swift @@ -183,7 +183,9 @@ class AppAssembly: Assembly { }.inObjectScope(.container) container.register(FirebaseAnalyticsManager.self) { r in - FirebaseAnalyticsManager() + FirebaseAnalyticsManager( + config: r.resolve(ConfigProtocol.self)! + ) }.inObjectScope(.container) } } diff --git a/OpenEdX/Managers/FirebaseManager/FirebaseAnalyticsManager.swift b/OpenEdX/Managers/FirebaseManager/FirebaseAnalyticsManager.swift index faf49a148..7f7ac3bc2 100644 --- a/OpenEdX/Managers/FirebaseManager/FirebaseAnalyticsManager.swift +++ b/OpenEdX/Managers/FirebaseManager/FirebaseAnalyticsManager.swift @@ -11,7 +11,9 @@ import Core class FirebaseAnalyticsManager: AnalyticsService { // Init manager - init() { + public init(config: ConfigProtocol) { + guard config.firebase.enabled && config.firebase.isAnalyticsSourceFirebase else { return } + FirebaseApp.configure() } diff --git a/OpenEdX/Managers/SegmentManager/SegmentAnalyticsManager.swift b/OpenEdX/Managers/SegmentManager/SegmentAnalyticsManager.swift index 960492a13..aafc2f16b 100644 --- a/OpenEdX/Managers/SegmentManager/SegmentAnalyticsManager.swift +++ b/OpenEdX/Managers/SegmentManager/SegmentAnalyticsManager.swift @@ -15,6 +15,8 @@ class SegmentAnalyticsManager: AnalyticsService { // Init manager public init(config: ConfigProtocol) { + guard config.segment.enabled else { return } + let configuration = Configuration(writeKey: config.segment.writeKey) .trackApplicationLifecycleEvents(true) .flushInterval(10) From 1c4939b036e0a06b38c3f2c73d3847987cddc5a3 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Wed, 21 Feb 2024 15:15:29 +0100 Subject: [PATCH 16/21] chore: renamed managers group --- OpenEdX.xcodeproj/project.pbxproj | 12 ++++++------ .../FirebaseAnalyticsManager.swift | 2 +- .../SegmentAnalyticsManager.swift | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) rename OpenEdX/Managers/{FirebaseManager => FirebaseAnalyticsManager}/FirebaseAnalyticsManager.swift (94%) rename OpenEdX/Managers/{SegmentManager => SegmentAnalyticsManager}/SegmentAnalyticsManager.swift (97%) diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index eee455ddb..01a45e0a3 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -293,8 +293,8 @@ A59568932B6162E400ED4F90 /* DeepLinkManager */, A50066872B613E4B0024680B /* PushNotificationsManager */, A50066892B613E990024680B /* AnalyticsManager */, - A59702272B83C84800CA064C /* FirebaseManager */, - A5C10D8D2B861A56008E864D /* SegmentManager */, + A59702272B83C84800CA064C /* FirebaseAnalyticsManager */, + A5C10D8D2B861A56008E864D /* SegmentAnalyticsManager */, ); path = Managers; sourceTree = ""; @@ -345,20 +345,20 @@ path = Link; sourceTree = ""; }; - A59702272B83C84800CA064C /* FirebaseManager */ = { + A59702272B83C84800CA064C /* FirebaseAnalyticsManager */ = { isa = PBXGroup; children = ( A59702282B83C87900CA064C /* FirebaseAnalyticsManager.swift */, ); - path = FirebaseManager; + path = FirebaseAnalyticsManager; sourceTree = ""; }; - A5C10D8D2B861A56008E864D /* SegmentManager */ = { + A5C10D8D2B861A56008E864D /* SegmentAnalyticsManager */ = { isa = PBXGroup; children = ( A5C10D8E2B861A70008E864D /* SegmentAnalyticsManager.swift */, ); - path = SegmentManager; + path = SegmentAnalyticsManager; sourceTree = ""; }; A5F46FD02B692B140003EEEF /* Services */ = { diff --git a/OpenEdX/Managers/FirebaseManager/FirebaseAnalyticsManager.swift b/OpenEdX/Managers/FirebaseAnalyticsManager/FirebaseAnalyticsManager.swift similarity index 94% rename from OpenEdX/Managers/FirebaseManager/FirebaseAnalyticsManager.swift rename to OpenEdX/Managers/FirebaseAnalyticsManager/FirebaseAnalyticsManager.swift index 7f7ac3bc2..6c1ac0343 100644 --- a/OpenEdX/Managers/FirebaseManager/FirebaseAnalyticsManager.swift +++ b/OpenEdX/Managers/FirebaseAnalyticsManager/FirebaseAnalyticsManager.swift @@ -1,5 +1,5 @@ // -// FirebaseManager.swift +// FirebaseAnalyticsManager.swift // OpenEdX // // Created by Anton Yarmolenka on 19/02/2024. diff --git a/OpenEdX/Managers/SegmentManager/SegmentAnalyticsManager.swift b/OpenEdX/Managers/SegmentAnalyticsManager/SegmentAnalyticsManager.swift similarity index 97% rename from OpenEdX/Managers/SegmentManager/SegmentAnalyticsManager.swift rename to OpenEdX/Managers/SegmentAnalyticsManager/SegmentAnalyticsManager.swift index aafc2f16b..ba3012518 100644 --- a/OpenEdX/Managers/SegmentManager/SegmentAnalyticsManager.swift +++ b/OpenEdX/Managers/SegmentAnalyticsManager/SegmentAnalyticsManager.swift @@ -1,5 +1,5 @@ // -// SegmentManager.swift +// SegmentAnalyticsManager.swift // OpenEdX // // Created by Anton Yarmolenka on 21/02/2024. From 5eb6e20134caa3708648e309fbc3382eb67fc80d Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Wed, 21 Feb 2024 16:18:44 +0100 Subject: [PATCH 17/21] chore: cleanup code --- OpenEdX.xcodeproj/project.pbxproj | 8 ++--- .../DeepLinkManager/BranchService.swift | 35 ------------------- .../DeepLinkManager/DeepLinkManager.swift | 2 -- .../Providers/BrazeProvider.swift | 1 - 4 files changed, 4 insertions(+), 42 deletions(-) delete mode 100644 OpenEdX/Managers/DeepLinkManager/BranchService.swift diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index 01a45e0a3..4a0109749 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -53,10 +53,10 @@ A51CDBE52B6D1E93009B6D4E /* Segment in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBE42B6D1E93009B6D4E /* Segment */; }; A51CDBED2B6D2BEE009B6D4E /* SegmentBraze in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBEC2B6D2BEE009B6D4E /* SegmentBraze */; }; A51CDBEF2B6D2BEE009B6D4E /* SegmentBrazeUI in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBEE2B6D2BEE009B6D4E /* SegmentBrazeUI */; }; + A5462D9C2B864AE0003B96A5 /* BranchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5462D9B2B864AE0003B96A5 /* BranchService.swift */; }; A59568952B61630500ED4F90 /* DeepLinkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568942B61630500ED4F90 /* DeepLinkManager.swift */; }; A59568972B61653700ED4F90 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568962B61653700ED4F90 /* DeepLink.swift */; }; A59568992B616D9400ED4F90 /* PushLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568982B616D9400ED4F90 /* PushLink.swift */; }; - A59585AF2B62A07100A35A20 /* BranchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59585AE2B62A07100A35A20 /* BranchService.swift */; }; A59702292B83C87900CA064C /* FirebaseAnalyticsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59702282B83C87900CA064C /* FirebaseAnalyticsManager.swift */; }; A5BD3E302B83B0F7006A8983 /* SegmentFirebase in Frameworks */ = {isa = PBXBuildFile; productRef = A5BD3E2F2B83B0F7006A8983 /* SegmentFirebase */; }; A5C10D8F2B861A70008E864D /* SegmentAnalyticsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5C10D8E2B861A70008E864D /* SegmentAnalyticsManager.swift */; }; @@ -135,10 +135,10 @@ A50066902B61467B0024680B /* BrazeProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrazeProvider.swift; sourceTree = ""; }; A50066922B614DCD0024680B /* FCMListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCMListener.swift; sourceTree = ""; }; A50066942B614DEF0024680B /* BrazeListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrazeListener.swift; sourceTree = ""; }; + A5462D9B2B864AE0003B96A5 /* BranchService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BranchService.swift; sourceTree = ""; }; A59568942B61630500ED4F90 /* DeepLinkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkManager.swift; sourceTree = ""; }; A59568962B61653700ED4F90 /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = ""; }; A59568982B616D9400ED4F90 /* PushLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushLink.swift; sourceTree = ""; }; - A59585AE2B62A07100A35A20 /* BranchService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BranchService.swift; path = ../BranchService.swift; sourceTree = ""; }; A59702282B83C87900CA064C /* FirebaseAnalyticsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseAnalyticsManager.swift; sourceTree = ""; }; A5C10D8E2B861A70008E864D /* SegmentAnalyticsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentAnalyticsManager.swift; sourceTree = ""; }; A89AD827F52CF6A6B903606E /* Pods-App-OpenEdX.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-OpenEdX.releasestage.xcconfig"; path = "Target Support Files/Pods-App-OpenEdX/Pods-App-OpenEdX.releasestage.xcconfig"; sourceTree = ""; }; @@ -364,7 +364,7 @@ A5F46FD02B692B140003EEEF /* Services */ = { isa = PBXGroup; children = ( - A59585AE2B62A07100A35A20 /* BranchService.swift */, + A5462D9B2B864AE0003B96A5 /* BranchService.swift */, ); path = Services; sourceTree = ""; @@ -550,7 +550,6 @@ 0293A2052A6FCD430090A336 /* CoursePersistence.swift in Sources */, 020CA5D92AA0A25300970AAF /* AppStorage.swift in Sources */, 0298DF302A4EF7230023A257 /* AnalyticsManager.swift in Sources */, - A59585AF2B62A07100A35A20 /* BranchService.swift in Sources */, A50066912B61467B0024680B /* BrazeProvider.swift in Sources */, 0293A2072A6FCDA30090A336 /* DiscoveryPersistence.swift in Sources */, 07D5DA3528D075AA00752FD9 /* AppDelegate.swift in Sources */, @@ -559,6 +558,7 @@ 0770DE5028D0A707006D8A5D /* NetworkAssembly.swift in Sources */, 0293A2032A6FCA590090A336 /* CorePersistence.swift in Sources */, 0770DE1E28D084E8006D8A5D /* AppAssembly.swift in Sources */, + A5462D9C2B864AE0003B96A5 /* BranchService.swift in Sources */, A50066932B614DCD0024680B /* FCMListener.swift in Sources */, A59702292B83C87900CA064C /* FirebaseAnalyticsManager.swift in Sources */, A500668D2B6143000024680B /* FCMProvider.swift in Sources */, diff --git a/OpenEdX/Managers/DeepLinkManager/BranchService.swift b/OpenEdX/Managers/DeepLinkManager/BranchService.swift deleted file mode 100644 index 4d36fb27d..000000000 --- a/OpenEdX/Managers/DeepLinkManager/BranchService.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// BranchService.swift -// OpenEdX -// -// Created by Anton Yarmolenka on 25/01/2024. -// - -import Foundation -import UIKit - -class BranchService: DeepLinkService { - // configure service - func configureWith(launchOptions: [UIApplication.LaunchOptionsKey: Any]?) { - - } - - // handle url - func handledURLWith( - app: UIApplication, - open url: URL, - options: [UIApplication.OpenURLOptionsKey: Any] - ) -> Bool { - false - } - - // This method process push notification with the link object - func processNotification(with link: PushLink) { - - } - - // This method process the deep link with response parameters - func processDeepLink(with params: [String: Any]) { - - } -} diff --git a/OpenEdX/Managers/DeepLinkManager/DeepLinkManager.swift b/OpenEdX/Managers/DeepLinkManager/DeepLinkManager.swift index 15be9c1d2..157bf2bb7 100644 --- a/OpenEdX/Managers/DeepLinkManager/DeepLinkManager.swift +++ b/OpenEdX/Managers/DeepLinkManager/DeepLinkManager.swift @@ -10,8 +10,6 @@ import Core import UIKit public protocol DeepLinkService { - func processNotification(with link: PushLink) - func processDeepLink(with params: [String: Any]) func configureWith(launchOptions: [UIApplication.LaunchOptionsKey: Any]?) func handledURLWith(app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool } diff --git a/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift b/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift index 7b21b21be..acaddc0ba 100644 --- a/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift +++ b/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift @@ -6,7 +6,6 @@ // import Foundation -import UIKit import SegmentBrazeUI import Swinject From af25b64b97c47eb37ad3ac79fe26933dbd4c17ea Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Wed, 21 Feb 2024 17:07:54 +0100 Subject: [PATCH 18/21] chore: re-added Segment package through https source --- OpenEdX.xcodeproj/project.pbxproj | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index 4a0109749..dbbde7246 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -50,10 +50,10 @@ A50066912B61467B0024680B /* BrazeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50066902B61467B0024680B /* BrazeProvider.swift */; }; A50066932B614DCD0024680B /* FCMListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50066922B614DCD0024680B /* FCMListener.swift */; }; A50066952B614DEF0024680B /* BrazeListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50066942B614DEF0024680B /* BrazeListener.swift */; }; - A51CDBE52B6D1E93009B6D4E /* Segment in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBE42B6D1E93009B6D4E /* Segment */; }; A51CDBED2B6D2BEE009B6D4E /* SegmentBraze in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBEC2B6D2BEE009B6D4E /* SegmentBraze */; }; A51CDBEF2B6D2BEE009B6D4E /* SegmentBrazeUI in Frameworks */ = {isa = PBXBuildFile; productRef = A51CDBEE2B6D2BEE009B6D4E /* SegmentBrazeUI */; }; A5462D9C2B864AE0003B96A5 /* BranchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5462D9B2B864AE0003B96A5 /* BranchService.swift */; }; + A5462D9F2B865713003B96A5 /* Segment in Frameworks */ = {isa = PBXBuildFile; productRef = A5462D9E2B865713003B96A5 /* Segment */; }; A59568952B61630500ED4F90 /* DeepLinkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568942B61630500ED4F90 /* DeepLinkManager.swift */; }; A59568972B61653700ED4F90 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568962B61653700ED4F90 /* DeepLink.swift */; }; A59568992B616D9400ED4F90 /* PushLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568982B616D9400ED4F90 /* PushLink.swift */; }; @@ -158,7 +158,7 @@ 025DE1A428DB4DAE0053E0F4 /* Profile.framework in Frameworks */, 0770DE4B28D0A462006D8A5D /* Authorization.framework in Frameworks */, BA3042792B1F7147009B64B7 /* MSAL in Frameworks */, - A51CDBE52B6D1E93009B6D4E /* Segment in Frameworks */, + A5462D9F2B865713003B96A5 /* Segment in Frameworks */, 072787B128D34D83002E9142 /* Discovery.framework in Frameworks */, 0218196428F734FA00202564 /* Discussion.framework in Frameworks */, 0219C67728F4347600D64452 /* Course.framework in Frameworks */, @@ -392,10 +392,10 @@ name = OpenEdX; packageProductDependencies = ( BA3042782B1F7147009B64B7 /* MSAL */, - A51CDBE42B6D1E93009B6D4E /* Segment */, A51CDBEC2B6D2BEE009B6D4E /* SegmentBraze */, A51CDBEE2B6D2BEE009B6D4E /* SegmentBrazeUI */, A5BD3E2F2B83B0F7006A8983 /* SegmentFirebase */, + A5462D9E2B865713003B96A5 /* Segment */, ); productName = OpenEdX; productReference = 07D5DA3128D075AA00752FD9 /* OpenEdX.app */; @@ -428,9 +428,9 @@ mainGroup = 07D5DA2828D075AA00752FD9; packageReferences = ( BA3042772B1F7147009B64B7 /* XCRemoteSwiftPackageReference "microsoft-authentication-library-for-objc" */, - A51CDBE32B6D1E93009B6D4E /* XCRemoteSwiftPackageReference "analytics-swift" */, A51CDBEB2B6D2BEE009B6D4E /* XCRemoteSwiftPackageReference "braze-segment-swift" */, A5BD3E2E2B83B0F7006A8983 /* XCRemoteSwiftPackageReference "analytics-swift-firebase" */, + A5462D9D2B865713003B96A5 /* XCRemoteSwiftPackageReference "analytics-swift" */, ); productRefGroup = 07D5DA3228D075AA00752FD9 /* Products */; projectDirPath = ""; @@ -1195,20 +1195,20 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - A51CDBE32B6D1E93009B6D4E /* XCRemoteSwiftPackageReference "analytics-swift" */ = { + A51CDBEB2B6D2BEE009B6D4E /* XCRemoteSwiftPackageReference "braze-segment-swift" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "git@github.com:segmentio/analytics-swift.git"; + repositoryURL = "https://github.com/braze-inc/braze-segment-swift"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 1.5.2; + minimumVersion = 2.2.0; }; }; - A51CDBEB2B6D2BEE009B6D4E /* XCRemoteSwiftPackageReference "braze-segment-swift" */ = { + A5462D9D2B865713003B96A5 /* XCRemoteSwiftPackageReference "analytics-swift" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/braze-inc/braze-segment-swift"; + repositoryURL = "https://github.com/segmentio/analytics-swift.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 2.2.0; + minimumVersion = 1.5.3; }; }; A5BD3E2E2B83B0F7006A8983 /* XCRemoteSwiftPackageReference "analytics-swift-firebase" */ = { @@ -1230,11 +1230,6 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - A51CDBE42B6D1E93009B6D4E /* Segment */ = { - isa = XCSwiftPackageProductDependency; - package = A51CDBE32B6D1E93009B6D4E /* XCRemoteSwiftPackageReference "analytics-swift" */; - productName = Segment; - }; A51CDBEC2B6D2BEE009B6D4E /* SegmentBraze */ = { isa = XCSwiftPackageProductDependency; package = A51CDBEB2B6D2BEE009B6D4E /* XCRemoteSwiftPackageReference "braze-segment-swift" */; @@ -1245,6 +1240,11 @@ package = A51CDBEB2B6D2BEE009B6D4E /* XCRemoteSwiftPackageReference "braze-segment-swift" */; productName = SegmentBrazeUI; }; + A5462D9E2B865713003B96A5 /* Segment */ = { + isa = XCSwiftPackageProductDependency; + package = A5462D9D2B865713003B96A5 /* XCRemoteSwiftPackageReference "analytics-swift" */; + productName = Segment; + }; A5BD3E2F2B83B0F7006A8983 /* SegmentFirebase */ = { isa = XCSwiftPackageProductDependency; package = A5BD3E2E2B83B0F7006A8983 /* XCRemoteSwiftPackageReference "analytics-swift-firebase" */; From 7c67115ad618948a8f40fd5564f466da2ba95398 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Wed, 21 Feb 2024 18:17:56 +0100 Subject: [PATCH 19/21] chore: generated mocks --- .../AuthorizationMock.generated.swift | 272 +++++++++++++++++- Course/CourseTests/CourseMock.generated.swift | 246 ++++++++++++++++ .../DashboardMock.generated.swift | 246 ++++++++++++++++ .../DiscoveryMock.generated.swift | 246 ++++++++++++++++ .../DiscussionMock.generated.swift | 246 ++++++++++++++++ .../ProfileTests/ProfileMock.generated.swift | 246 ++++++++++++++++ 6 files changed, 1490 insertions(+), 12 deletions(-) diff --git a/Authorization/AuthorizationTests/AuthorizationMock.generated.swift b/Authorization/AuthorizationTests/AuthorizationMock.generated.swift index fbbb73a12..8a9b75959 100644 --- a/Authorization/AuthorizationTests/AuthorizationMock.generated.swift +++ b/Authorization/AuthorizationTests/AuthorizationMock.generated.swift @@ -509,10 +509,10 @@ open class AuthorizationAnalyticsMock: AuthorizationAnalytics, Mock { - open func setUserID(_ id: String) { - addInvocation(.m_setUserID__id(Parameter.value(`id`))) - let perform = methodPerformValue(.m_setUserID__id(Parameter.value(`id`))) as? (String) -> Void - perform?(`id`) + open func identify(id: String, username: String, email: String) { + addInvocation(.m_identify__id_idusername_usernameemail_email(Parameter.value(`id`), Parameter.value(`username`), Parameter.value(`email`))) + let perform = methodPerformValue(.m_identify__id_idusername_usernameemail_email(Parameter.value(`id`), Parameter.value(`username`), Parameter.value(`email`))) as? (String, String, String) -> Void + perform?(`id`, `username`, `email`) } open func userLogin(method: AuthMethod) { @@ -553,7 +553,7 @@ open class AuthorizationAnalyticsMock: AuthorizationAnalytics, Mock { fileprivate enum MethodType { - case m_setUserID__id(Parameter) + case m_identify__id_idusername_usernameemail_email(Parameter, Parameter, Parameter) case m_userLogin__method_method(Parameter) case m_signUpClicked case m_createAccountClicked @@ -563,9 +563,11 @@ open class AuthorizationAnalyticsMock: AuthorizationAnalytics, Mock { static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { switch (lhs, rhs) { - case (.m_setUserID__id(let lhsId), .m_setUserID__id(let rhsId)): + case (.m_identify__id_idusername_usernameemail_email(let lhsId, let lhsUsername, let lhsEmail), .m_identify__id_idusername_usernameemail_email(let rhsId, let rhsUsername, let rhsEmail)): var results: [Matcher.ParameterComparisonResult] = [] - results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsId, rhs: rhsId, with: matcher), lhsId, rhsId, "_ id")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsId, rhs: rhsId, with: matcher), lhsId, rhsId, "id")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsUsername, rhs: rhsUsername, with: matcher), lhsUsername, rhsUsername, "username")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsEmail, rhs: rhsEmail, with: matcher), lhsEmail, rhsEmail, "email")) return Matcher.ComparisonResult(results) case (.m_userLogin__method_method(let lhsMethod), .m_userLogin__method_method(let rhsMethod)): @@ -591,7 +593,7 @@ open class AuthorizationAnalyticsMock: AuthorizationAnalytics, Mock { func intValue() -> Int { switch self { - case let .m_setUserID__id(p0): return p0.intValue + case let .m_identify__id_idusername_usernameemail_email(p0, p1, p2): return p0.intValue + p1.intValue + p2.intValue case let .m_userLogin__method_method(p0): return p0.intValue case .m_signUpClicked: return 0 case .m_createAccountClicked: return 0 @@ -602,7 +604,7 @@ open class AuthorizationAnalyticsMock: AuthorizationAnalytics, Mock { } func assertionName() -> String { switch self { - case .m_setUserID__id: return ".setUserID(_:)" + case .m_identify__id_idusername_usernameemail_email: return ".identify(id:username:email:)" case .m_userLogin__method_method: return ".userLogin(method:)" case .m_signUpClicked: return ".signUpClicked()" case .m_createAccountClicked: return ".createAccountClicked()" @@ -627,7 +629,7 @@ open class AuthorizationAnalyticsMock: AuthorizationAnalytics, Mock { public struct Verify { fileprivate var method: MethodType - public static func setUserID(_ id: Parameter) -> Verify { return Verify(method: .m_setUserID__id(`id`))} + public static func identify(id: Parameter, username: Parameter, email: Parameter) -> Verify { return Verify(method: .m_identify__id_idusername_usernameemail_email(`id`, `username`, `email`))} public static func userLogin(method: Parameter) -> Verify { return Verify(method: .m_userLogin__method_method(`method`))} public static func signUpClicked() -> Verify { return Verify(method: .m_signUpClicked)} public static func createAccountClicked() -> Verify { return Verify(method: .m_createAccountClicked)} @@ -640,8 +642,8 @@ open class AuthorizationAnalyticsMock: AuthorizationAnalytics, Mock { fileprivate var method: MethodType var performs: Any - public static func setUserID(_ id: Parameter, perform: @escaping (String) -> Void) -> Perform { - return Perform(method: .m_setUserID__id(`id`), performs: perform) + public static func identify(id: Parameter, username: Parameter, email: Parameter, perform: @escaping (String, String, String) -> Void) -> Perform { + return Perform(method: .m_identify__id_idusername_usernameemail_email(`id`, `username`, `email`), performs: perform) } public static func userLogin(method: Parameter, perform: @escaping (AuthMethod) -> Void) -> Perform { return Perform(method: .m_userLogin__method_method(`method`), performs: perform) @@ -2436,3 +2438,249 @@ open class DownloadManagerProtocolMock: DownloadManagerProtocol, Mock { } } +// MARK: - WebviewCookiesUpdateProtocol + +open class WebviewCookiesUpdateProtocolMock: WebviewCookiesUpdateProtocol, Mock { + public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { + SwiftyMockyTestObserver.setup() + self.sequencingPolicy = sequencingPolicy + self.stubbingPolicy = stubbingPolicy + self.file = file + self.line = line + } + + var matcher: Matcher = Matcher.default + var stubbingPolicy: StubbingPolicy = .wrap + var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst + + private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) + private var invocations: [MethodType] = [] + private var methodReturnValues: [Given] = [] + private var methodPerformValues: [Perform] = [] + private var file: StaticString? + private var line: UInt? + + public typealias PropertyStub = Given + public typealias MethodStub = Given + public typealias SubscriptStub = Given + + /// Convenience method - call setupMock() to extend debug information when failure occurs + public func setupMock(file: StaticString = #file, line: UInt = #line) { + self.file = file + self.line = line + } + + /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals + public func resetMock(_ scopes: MockScope...) { + let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes + if scopes.contains(.invocation) { invocations = [] } + if scopes.contains(.given) { methodReturnValues = [] } + if scopes.contains(.perform) { methodPerformValues = [] } + } + + public var authInteractor: AuthInteractorProtocol { + get { invocations.append(.p_authInteractor_get); return __p_authInteractor ?? givenGetterValue(.p_authInteractor_get, "WebviewCookiesUpdateProtocolMock - stub value for authInteractor was not defined") } + } + private var __p_authInteractor: (AuthInteractorProtocol)? + + public var cookiesReady: Bool { + get { invocations.append(.p_cookiesReady_get); return __p_cookiesReady ?? givenGetterValue(.p_cookiesReady_get, "WebviewCookiesUpdateProtocolMock - stub value for cookiesReady was not defined") } + set { invocations.append(.p_cookiesReady_set(.value(newValue))); __p_cookiesReady = newValue } + } + private var __p_cookiesReady: (Bool)? + + public var updatingCookies: Bool { + get { invocations.append(.p_updatingCookies_get); return __p_updatingCookies ?? givenGetterValue(.p_updatingCookies_get, "WebviewCookiesUpdateProtocolMock - stub value for updatingCookies was not defined") } + set { invocations.append(.p_updatingCookies_set(.value(newValue))); __p_updatingCookies = newValue } + } + private var __p_updatingCookies: (Bool)? + + public var errorMessage: String? { + get { invocations.append(.p_errorMessage_get); return __p_errorMessage ?? optionalGivenGetterValue(.p_errorMessage_get, "WebviewCookiesUpdateProtocolMock - stub value for errorMessage was not defined") } + set { invocations.append(.p_errorMessage_set(.value(newValue))); __p_errorMessage = newValue } + } + private var __p_errorMessage: (String)? + + + + + + open func updateCookies(force: Bool, retryCount: Int) { + addInvocation(.m_updateCookies__force_forceretryCount_retryCount(Parameter.value(`force`), Parameter.value(`retryCount`))) + let perform = methodPerformValue(.m_updateCookies__force_forceretryCount_retryCount(Parameter.value(`force`), Parameter.value(`retryCount`))) as? (Bool, Int) -> Void + perform?(`force`, `retryCount`) + } + + + fileprivate enum MethodType { + case m_updateCookies__force_forceretryCount_retryCount(Parameter, Parameter) + case p_authInteractor_get + case p_cookiesReady_get + case p_cookiesReady_set(Parameter) + case p_updatingCookies_get + case p_updatingCookies_set(Parameter) + case p_errorMessage_get + case p_errorMessage_set(Parameter) + + static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { + switch (lhs, rhs) { + case (.m_updateCookies__force_forceretryCount_retryCount(let lhsForce, let lhsRetrycount), .m_updateCookies__force_forceretryCount_retryCount(let rhsForce, let rhsRetrycount)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsForce, rhs: rhsForce, with: matcher), lhsForce, rhsForce, "force")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsRetrycount, rhs: rhsRetrycount, with: matcher), lhsRetrycount, rhsRetrycount, "retryCount")) + return Matcher.ComparisonResult(results) + case (.p_authInteractor_get,.p_authInteractor_get): return Matcher.ComparisonResult.match + case (.p_cookiesReady_get,.p_cookiesReady_get): return Matcher.ComparisonResult.match + case (.p_cookiesReady_set(let left),.p_cookiesReady_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_updatingCookies_get,.p_updatingCookies_get): return Matcher.ComparisonResult.match + case (.p_updatingCookies_set(let left),.p_updatingCookies_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_errorMessage_get,.p_errorMessage_get): return Matcher.ComparisonResult.match + case (.p_errorMessage_set(let left),.p_errorMessage_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + default: return .none + } + } + + func intValue() -> Int { + switch self { + case let .m_updateCookies__force_forceretryCount_retryCount(p0, p1): return p0.intValue + p1.intValue + case .p_authInteractor_get: return 0 + case .p_cookiesReady_get: return 0 + case .p_cookiesReady_set(let newValue): return newValue.intValue + case .p_updatingCookies_get: return 0 + case .p_updatingCookies_set(let newValue): return newValue.intValue + case .p_errorMessage_get: return 0 + case .p_errorMessage_set(let newValue): return newValue.intValue + } + } + func assertionName() -> String { + switch self { + case .m_updateCookies__force_forceretryCount_retryCount: return ".updateCookies(force:retryCount:)" + case .p_authInteractor_get: return "[get] .authInteractor" + case .p_cookiesReady_get: return "[get] .cookiesReady" + case .p_cookiesReady_set: return "[set] .cookiesReady" + case .p_updatingCookies_get: return "[get] .updatingCookies" + case .p_updatingCookies_set: return "[set] .updatingCookies" + case .p_errorMessage_get: return "[get] .errorMessage" + case .p_errorMessage_set: return "[set] .errorMessage" + } + } + } + + open class Given: StubbedMethod { + fileprivate var method: MethodType + + private init(method: MethodType, products: [StubProduct]) { + self.method = method + super.init(products) + } + + public static func authInteractor(getter defaultValue: AuthInteractorProtocol...) -> PropertyStub { + return Given(method: .p_authInteractor_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func cookiesReady(getter defaultValue: Bool...) -> PropertyStub { + return Given(method: .p_cookiesReady_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func updatingCookies(getter defaultValue: Bool...) -> PropertyStub { + return Given(method: .p_updatingCookies_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func errorMessage(getter defaultValue: String?...) -> PropertyStub { + return Given(method: .p_errorMessage_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + + } + + public struct Verify { + fileprivate var method: MethodType + + public static func updateCookies(force: Parameter, retryCount: Parameter) -> Verify { return Verify(method: .m_updateCookies__force_forceretryCount_retryCount(`force`, `retryCount`))} + public static var authInteractor: Verify { return Verify(method: .p_authInteractor_get) } + public static var cookiesReady: Verify { return Verify(method: .p_cookiesReady_get) } + public static func cookiesReady(set newValue: Parameter) -> Verify { return Verify(method: .p_cookiesReady_set(newValue)) } + public static var updatingCookies: Verify { return Verify(method: .p_updatingCookies_get) } + public static func updatingCookies(set newValue: Parameter) -> Verify { return Verify(method: .p_updatingCookies_set(newValue)) } + public static var errorMessage: Verify { return Verify(method: .p_errorMessage_get) } + public static func errorMessage(set newValue: Parameter) -> Verify { return Verify(method: .p_errorMessage_set(newValue)) } + } + + public struct Perform { + fileprivate var method: MethodType + var performs: Any + + public static func updateCookies(force: Parameter, retryCount: Parameter, perform: @escaping (Bool, Int) -> Void) -> Perform { + return Perform(method: .m_updateCookies__force_forceretryCount_retryCount(`force`, `retryCount`), performs: perform) + } + } + + public func given(_ method: Given) { + methodReturnValues.append(method) + } + + public func perform(_ method: Perform) { + methodPerformValues.append(method) + methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } + } + + public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { + let fullMatches = matchingCalls(method, file: file, line: line) + let success = count.matches(fullMatches) + let assertionName = method.method.assertionName() + let feedback: String = { + guard !success else { return "" } + return Utils.closestCallsMessage( + for: self.invocations.map { invocation in + matcher.set(file: file, line: line) + defer { matcher.clearFileAndLine() } + return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) + }, + name: assertionName + ) + }() + MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) + } + + private func addInvocation(_ call: MethodType) { + self.queue.sync { invocations.append(call) } + } + private func methodReturnValue(_ method: MethodType) throws -> StubProduct { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) + let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) + guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } + return product + } + private func methodPerformValue(_ method: MethodType) -> Any? { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } + return matched?.performs + } + private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { + matcher.set(file: file ?? self.file, line: line ?? self.line) + defer { matcher.clearFileAndLine() } + return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } + } + private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { + return matchingCalls(method.method, file: file, line: line).count + } + private func givenGetterValue(_ method: MethodType, _ message: String) -> T { + do { + return try methodReturnValue(method).casted() + } catch { + onFatalFailure(message) + Failure(message) + } + } + private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { + do { + return try methodReturnValue(method).casted() + } catch { + return nil + } + } + private func onFatalFailure(_ message: String) { + guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully + SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) + } +} + diff --git a/Course/CourseTests/CourseMock.generated.swift b/Course/CourseTests/CourseMock.generated.swift index 3acd549eb..ba210fd9b 100644 --- a/Course/CourseTests/CourseMock.generated.swift +++ b/Course/CourseTests/CourseMock.generated.swift @@ -2628,3 +2628,249 @@ open class DownloadManagerProtocolMock: DownloadManagerProtocol, Mock { } } +// MARK: - WebviewCookiesUpdateProtocol + +open class WebviewCookiesUpdateProtocolMock: WebviewCookiesUpdateProtocol, Mock { + public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { + SwiftyMockyTestObserver.setup() + self.sequencingPolicy = sequencingPolicy + self.stubbingPolicy = stubbingPolicy + self.file = file + self.line = line + } + + var matcher: Matcher = Matcher.default + var stubbingPolicy: StubbingPolicy = .wrap + var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst + + private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) + private var invocations: [MethodType] = [] + private var methodReturnValues: [Given] = [] + private var methodPerformValues: [Perform] = [] + private var file: StaticString? + private var line: UInt? + + public typealias PropertyStub = Given + public typealias MethodStub = Given + public typealias SubscriptStub = Given + + /// Convenience method - call setupMock() to extend debug information when failure occurs + public func setupMock(file: StaticString = #file, line: UInt = #line) { + self.file = file + self.line = line + } + + /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals + public func resetMock(_ scopes: MockScope...) { + let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes + if scopes.contains(.invocation) { invocations = [] } + if scopes.contains(.given) { methodReturnValues = [] } + if scopes.contains(.perform) { methodPerformValues = [] } + } + + public var authInteractor: AuthInteractorProtocol { + get { invocations.append(.p_authInteractor_get); return __p_authInteractor ?? givenGetterValue(.p_authInteractor_get, "WebviewCookiesUpdateProtocolMock - stub value for authInteractor was not defined") } + } + private var __p_authInteractor: (AuthInteractorProtocol)? + + public var cookiesReady: Bool { + get { invocations.append(.p_cookiesReady_get); return __p_cookiesReady ?? givenGetterValue(.p_cookiesReady_get, "WebviewCookiesUpdateProtocolMock - stub value for cookiesReady was not defined") } + set { invocations.append(.p_cookiesReady_set(.value(newValue))); __p_cookiesReady = newValue } + } + private var __p_cookiesReady: (Bool)? + + public var updatingCookies: Bool { + get { invocations.append(.p_updatingCookies_get); return __p_updatingCookies ?? givenGetterValue(.p_updatingCookies_get, "WebviewCookiesUpdateProtocolMock - stub value for updatingCookies was not defined") } + set { invocations.append(.p_updatingCookies_set(.value(newValue))); __p_updatingCookies = newValue } + } + private var __p_updatingCookies: (Bool)? + + public var errorMessage: String? { + get { invocations.append(.p_errorMessage_get); return __p_errorMessage ?? optionalGivenGetterValue(.p_errorMessage_get, "WebviewCookiesUpdateProtocolMock - stub value for errorMessage was not defined") } + set { invocations.append(.p_errorMessage_set(.value(newValue))); __p_errorMessage = newValue } + } + private var __p_errorMessage: (String)? + + + + + + open func updateCookies(force: Bool, retryCount: Int) { + addInvocation(.m_updateCookies__force_forceretryCount_retryCount(Parameter.value(`force`), Parameter.value(`retryCount`))) + let perform = methodPerformValue(.m_updateCookies__force_forceretryCount_retryCount(Parameter.value(`force`), Parameter.value(`retryCount`))) as? (Bool, Int) -> Void + perform?(`force`, `retryCount`) + } + + + fileprivate enum MethodType { + case m_updateCookies__force_forceretryCount_retryCount(Parameter, Parameter) + case p_authInteractor_get + case p_cookiesReady_get + case p_cookiesReady_set(Parameter) + case p_updatingCookies_get + case p_updatingCookies_set(Parameter) + case p_errorMessage_get + case p_errorMessage_set(Parameter) + + static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { + switch (lhs, rhs) { + case (.m_updateCookies__force_forceretryCount_retryCount(let lhsForce, let lhsRetrycount), .m_updateCookies__force_forceretryCount_retryCount(let rhsForce, let rhsRetrycount)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsForce, rhs: rhsForce, with: matcher), lhsForce, rhsForce, "force")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsRetrycount, rhs: rhsRetrycount, with: matcher), lhsRetrycount, rhsRetrycount, "retryCount")) + return Matcher.ComparisonResult(results) + case (.p_authInteractor_get,.p_authInteractor_get): return Matcher.ComparisonResult.match + case (.p_cookiesReady_get,.p_cookiesReady_get): return Matcher.ComparisonResult.match + case (.p_cookiesReady_set(let left),.p_cookiesReady_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_updatingCookies_get,.p_updatingCookies_get): return Matcher.ComparisonResult.match + case (.p_updatingCookies_set(let left),.p_updatingCookies_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_errorMessage_get,.p_errorMessage_get): return Matcher.ComparisonResult.match + case (.p_errorMessage_set(let left),.p_errorMessage_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + default: return .none + } + } + + func intValue() -> Int { + switch self { + case let .m_updateCookies__force_forceretryCount_retryCount(p0, p1): return p0.intValue + p1.intValue + case .p_authInteractor_get: return 0 + case .p_cookiesReady_get: return 0 + case .p_cookiesReady_set(let newValue): return newValue.intValue + case .p_updatingCookies_get: return 0 + case .p_updatingCookies_set(let newValue): return newValue.intValue + case .p_errorMessage_get: return 0 + case .p_errorMessage_set(let newValue): return newValue.intValue + } + } + func assertionName() -> String { + switch self { + case .m_updateCookies__force_forceretryCount_retryCount: return ".updateCookies(force:retryCount:)" + case .p_authInteractor_get: return "[get] .authInteractor" + case .p_cookiesReady_get: return "[get] .cookiesReady" + case .p_cookiesReady_set: return "[set] .cookiesReady" + case .p_updatingCookies_get: return "[get] .updatingCookies" + case .p_updatingCookies_set: return "[set] .updatingCookies" + case .p_errorMessage_get: return "[get] .errorMessage" + case .p_errorMessage_set: return "[set] .errorMessage" + } + } + } + + open class Given: StubbedMethod { + fileprivate var method: MethodType + + private init(method: MethodType, products: [StubProduct]) { + self.method = method + super.init(products) + } + + public static func authInteractor(getter defaultValue: AuthInteractorProtocol...) -> PropertyStub { + return Given(method: .p_authInteractor_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func cookiesReady(getter defaultValue: Bool...) -> PropertyStub { + return Given(method: .p_cookiesReady_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func updatingCookies(getter defaultValue: Bool...) -> PropertyStub { + return Given(method: .p_updatingCookies_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func errorMessage(getter defaultValue: String?...) -> PropertyStub { + return Given(method: .p_errorMessage_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + + } + + public struct Verify { + fileprivate var method: MethodType + + public static func updateCookies(force: Parameter, retryCount: Parameter) -> Verify { return Verify(method: .m_updateCookies__force_forceretryCount_retryCount(`force`, `retryCount`))} + public static var authInteractor: Verify { return Verify(method: .p_authInteractor_get) } + public static var cookiesReady: Verify { return Verify(method: .p_cookiesReady_get) } + public static func cookiesReady(set newValue: Parameter) -> Verify { return Verify(method: .p_cookiesReady_set(newValue)) } + public static var updatingCookies: Verify { return Verify(method: .p_updatingCookies_get) } + public static func updatingCookies(set newValue: Parameter) -> Verify { return Verify(method: .p_updatingCookies_set(newValue)) } + public static var errorMessage: Verify { return Verify(method: .p_errorMessage_get) } + public static func errorMessage(set newValue: Parameter) -> Verify { return Verify(method: .p_errorMessage_set(newValue)) } + } + + public struct Perform { + fileprivate var method: MethodType + var performs: Any + + public static func updateCookies(force: Parameter, retryCount: Parameter, perform: @escaping (Bool, Int) -> Void) -> Perform { + return Perform(method: .m_updateCookies__force_forceretryCount_retryCount(`force`, `retryCount`), performs: perform) + } + } + + public func given(_ method: Given) { + methodReturnValues.append(method) + } + + public func perform(_ method: Perform) { + methodPerformValues.append(method) + methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } + } + + public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { + let fullMatches = matchingCalls(method, file: file, line: line) + let success = count.matches(fullMatches) + let assertionName = method.method.assertionName() + let feedback: String = { + guard !success else { return "" } + return Utils.closestCallsMessage( + for: self.invocations.map { invocation in + matcher.set(file: file, line: line) + defer { matcher.clearFileAndLine() } + return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) + }, + name: assertionName + ) + }() + MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) + } + + private func addInvocation(_ call: MethodType) { + self.queue.sync { invocations.append(call) } + } + private func methodReturnValue(_ method: MethodType) throws -> StubProduct { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) + let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) + guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } + return product + } + private func methodPerformValue(_ method: MethodType) -> Any? { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } + return matched?.performs + } + private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { + matcher.set(file: file ?? self.file, line: line ?? self.line) + defer { matcher.clearFileAndLine() } + return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } + } + private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { + return matchingCalls(method.method, file: file, line: line).count + } + private func givenGetterValue(_ method: MethodType, _ message: String) -> T { + do { + return try methodReturnValue(method).casted() + } catch { + onFatalFailure(message) + Failure(message) + } + } + private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { + do { + return try methodReturnValue(method).casted() + } catch { + return nil + } + } + private func onFatalFailure(_ message: String) { + guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully + SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) + } +} + diff --git a/Dashboard/DashboardTests/DashboardMock.generated.swift b/Dashboard/DashboardTests/DashboardMock.generated.swift index 2aa0f593d..221f07f4c 100644 --- a/Dashboard/DashboardTests/DashboardMock.generated.swift +++ b/Dashboard/DashboardTests/DashboardMock.generated.swift @@ -2106,3 +2106,249 @@ open class DownloadManagerProtocolMock: DownloadManagerProtocol, Mock { } } +// MARK: - WebviewCookiesUpdateProtocol + +open class WebviewCookiesUpdateProtocolMock: WebviewCookiesUpdateProtocol, Mock { + public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { + SwiftyMockyTestObserver.setup() + self.sequencingPolicy = sequencingPolicy + self.stubbingPolicy = stubbingPolicy + self.file = file + self.line = line + } + + var matcher: Matcher = Matcher.default + var stubbingPolicy: StubbingPolicy = .wrap + var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst + + private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) + private var invocations: [MethodType] = [] + private var methodReturnValues: [Given] = [] + private var methodPerformValues: [Perform] = [] + private var file: StaticString? + private var line: UInt? + + public typealias PropertyStub = Given + public typealias MethodStub = Given + public typealias SubscriptStub = Given + + /// Convenience method - call setupMock() to extend debug information when failure occurs + public func setupMock(file: StaticString = #file, line: UInt = #line) { + self.file = file + self.line = line + } + + /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals + public func resetMock(_ scopes: MockScope...) { + let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes + if scopes.contains(.invocation) { invocations = [] } + if scopes.contains(.given) { methodReturnValues = [] } + if scopes.contains(.perform) { methodPerformValues = [] } + } + + public var authInteractor: AuthInteractorProtocol { + get { invocations.append(.p_authInteractor_get); return __p_authInteractor ?? givenGetterValue(.p_authInteractor_get, "WebviewCookiesUpdateProtocolMock - stub value for authInteractor was not defined") } + } + private var __p_authInteractor: (AuthInteractorProtocol)? + + public var cookiesReady: Bool { + get { invocations.append(.p_cookiesReady_get); return __p_cookiesReady ?? givenGetterValue(.p_cookiesReady_get, "WebviewCookiesUpdateProtocolMock - stub value for cookiesReady was not defined") } + set { invocations.append(.p_cookiesReady_set(.value(newValue))); __p_cookiesReady = newValue } + } + private var __p_cookiesReady: (Bool)? + + public var updatingCookies: Bool { + get { invocations.append(.p_updatingCookies_get); return __p_updatingCookies ?? givenGetterValue(.p_updatingCookies_get, "WebviewCookiesUpdateProtocolMock - stub value for updatingCookies was not defined") } + set { invocations.append(.p_updatingCookies_set(.value(newValue))); __p_updatingCookies = newValue } + } + private var __p_updatingCookies: (Bool)? + + public var errorMessage: String? { + get { invocations.append(.p_errorMessage_get); return __p_errorMessage ?? optionalGivenGetterValue(.p_errorMessage_get, "WebviewCookiesUpdateProtocolMock - stub value for errorMessage was not defined") } + set { invocations.append(.p_errorMessage_set(.value(newValue))); __p_errorMessage = newValue } + } + private var __p_errorMessage: (String)? + + + + + + open func updateCookies(force: Bool, retryCount: Int) { + addInvocation(.m_updateCookies__force_forceretryCount_retryCount(Parameter.value(`force`), Parameter.value(`retryCount`))) + let perform = methodPerformValue(.m_updateCookies__force_forceretryCount_retryCount(Parameter.value(`force`), Parameter.value(`retryCount`))) as? (Bool, Int) -> Void + perform?(`force`, `retryCount`) + } + + + fileprivate enum MethodType { + case m_updateCookies__force_forceretryCount_retryCount(Parameter, Parameter) + case p_authInteractor_get + case p_cookiesReady_get + case p_cookiesReady_set(Parameter) + case p_updatingCookies_get + case p_updatingCookies_set(Parameter) + case p_errorMessage_get + case p_errorMessage_set(Parameter) + + static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { + switch (lhs, rhs) { + case (.m_updateCookies__force_forceretryCount_retryCount(let lhsForce, let lhsRetrycount), .m_updateCookies__force_forceretryCount_retryCount(let rhsForce, let rhsRetrycount)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsForce, rhs: rhsForce, with: matcher), lhsForce, rhsForce, "force")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsRetrycount, rhs: rhsRetrycount, with: matcher), lhsRetrycount, rhsRetrycount, "retryCount")) + return Matcher.ComparisonResult(results) + case (.p_authInteractor_get,.p_authInteractor_get): return Matcher.ComparisonResult.match + case (.p_cookiesReady_get,.p_cookiesReady_get): return Matcher.ComparisonResult.match + case (.p_cookiesReady_set(let left),.p_cookiesReady_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_updatingCookies_get,.p_updatingCookies_get): return Matcher.ComparisonResult.match + case (.p_updatingCookies_set(let left),.p_updatingCookies_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_errorMessage_get,.p_errorMessage_get): return Matcher.ComparisonResult.match + case (.p_errorMessage_set(let left),.p_errorMessage_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + default: return .none + } + } + + func intValue() -> Int { + switch self { + case let .m_updateCookies__force_forceretryCount_retryCount(p0, p1): return p0.intValue + p1.intValue + case .p_authInteractor_get: return 0 + case .p_cookiesReady_get: return 0 + case .p_cookiesReady_set(let newValue): return newValue.intValue + case .p_updatingCookies_get: return 0 + case .p_updatingCookies_set(let newValue): return newValue.intValue + case .p_errorMessage_get: return 0 + case .p_errorMessage_set(let newValue): return newValue.intValue + } + } + func assertionName() -> String { + switch self { + case .m_updateCookies__force_forceretryCount_retryCount: return ".updateCookies(force:retryCount:)" + case .p_authInteractor_get: return "[get] .authInteractor" + case .p_cookiesReady_get: return "[get] .cookiesReady" + case .p_cookiesReady_set: return "[set] .cookiesReady" + case .p_updatingCookies_get: return "[get] .updatingCookies" + case .p_updatingCookies_set: return "[set] .updatingCookies" + case .p_errorMessage_get: return "[get] .errorMessage" + case .p_errorMessage_set: return "[set] .errorMessage" + } + } + } + + open class Given: StubbedMethod { + fileprivate var method: MethodType + + private init(method: MethodType, products: [StubProduct]) { + self.method = method + super.init(products) + } + + public static func authInteractor(getter defaultValue: AuthInteractorProtocol...) -> PropertyStub { + return Given(method: .p_authInteractor_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func cookiesReady(getter defaultValue: Bool...) -> PropertyStub { + return Given(method: .p_cookiesReady_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func updatingCookies(getter defaultValue: Bool...) -> PropertyStub { + return Given(method: .p_updatingCookies_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func errorMessage(getter defaultValue: String?...) -> PropertyStub { + return Given(method: .p_errorMessage_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + + } + + public struct Verify { + fileprivate var method: MethodType + + public static func updateCookies(force: Parameter, retryCount: Parameter) -> Verify { return Verify(method: .m_updateCookies__force_forceretryCount_retryCount(`force`, `retryCount`))} + public static var authInteractor: Verify { return Verify(method: .p_authInteractor_get) } + public static var cookiesReady: Verify { return Verify(method: .p_cookiesReady_get) } + public static func cookiesReady(set newValue: Parameter) -> Verify { return Verify(method: .p_cookiesReady_set(newValue)) } + public static var updatingCookies: Verify { return Verify(method: .p_updatingCookies_get) } + public static func updatingCookies(set newValue: Parameter) -> Verify { return Verify(method: .p_updatingCookies_set(newValue)) } + public static var errorMessage: Verify { return Verify(method: .p_errorMessage_get) } + public static func errorMessage(set newValue: Parameter) -> Verify { return Verify(method: .p_errorMessage_set(newValue)) } + } + + public struct Perform { + fileprivate var method: MethodType + var performs: Any + + public static func updateCookies(force: Parameter, retryCount: Parameter, perform: @escaping (Bool, Int) -> Void) -> Perform { + return Perform(method: .m_updateCookies__force_forceretryCount_retryCount(`force`, `retryCount`), performs: perform) + } + } + + public func given(_ method: Given) { + methodReturnValues.append(method) + } + + public func perform(_ method: Perform) { + methodPerformValues.append(method) + methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } + } + + public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { + let fullMatches = matchingCalls(method, file: file, line: line) + let success = count.matches(fullMatches) + let assertionName = method.method.assertionName() + let feedback: String = { + guard !success else { return "" } + return Utils.closestCallsMessage( + for: self.invocations.map { invocation in + matcher.set(file: file, line: line) + defer { matcher.clearFileAndLine() } + return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) + }, + name: assertionName + ) + }() + MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) + } + + private func addInvocation(_ call: MethodType) { + self.queue.sync { invocations.append(call) } + } + private func methodReturnValue(_ method: MethodType) throws -> StubProduct { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) + let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) + guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } + return product + } + private func methodPerformValue(_ method: MethodType) -> Any? { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } + return matched?.performs + } + private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { + matcher.set(file: file ?? self.file, line: line ?? self.line) + defer { matcher.clearFileAndLine() } + return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } + } + private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { + return matchingCalls(method.method, file: file, line: line).count + } + private func givenGetterValue(_ method: MethodType, _ message: String) -> T { + do { + return try methodReturnValue(method).casted() + } catch { + onFatalFailure(message) + Failure(message) + } + } + private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { + do { + return try methodReturnValue(method).casted() + } catch { + return nil + } + } + private func onFatalFailure(_ message: String) { + guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully + SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) + } +} + diff --git a/Discovery/DiscoveryTests/DiscoveryMock.generated.swift b/Discovery/DiscoveryTests/DiscoveryMock.generated.swift index 3522e8691..38bdf1d91 100644 --- a/Discovery/DiscoveryTests/DiscoveryMock.generated.swift +++ b/Discovery/DiscoveryTests/DiscoveryMock.generated.swift @@ -2363,3 +2363,249 @@ open class DownloadManagerProtocolMock: DownloadManagerProtocol, Mock { } } +// MARK: - WebviewCookiesUpdateProtocol + +open class WebviewCookiesUpdateProtocolMock: WebviewCookiesUpdateProtocol, Mock { + public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { + SwiftyMockyTestObserver.setup() + self.sequencingPolicy = sequencingPolicy + self.stubbingPolicy = stubbingPolicy + self.file = file + self.line = line + } + + var matcher: Matcher = Matcher.default + var stubbingPolicy: StubbingPolicy = .wrap + var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst + + private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) + private var invocations: [MethodType] = [] + private var methodReturnValues: [Given] = [] + private var methodPerformValues: [Perform] = [] + private var file: StaticString? + private var line: UInt? + + public typealias PropertyStub = Given + public typealias MethodStub = Given + public typealias SubscriptStub = Given + + /// Convenience method - call setupMock() to extend debug information when failure occurs + public func setupMock(file: StaticString = #file, line: UInt = #line) { + self.file = file + self.line = line + } + + /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals + public func resetMock(_ scopes: MockScope...) { + let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes + if scopes.contains(.invocation) { invocations = [] } + if scopes.contains(.given) { methodReturnValues = [] } + if scopes.contains(.perform) { methodPerformValues = [] } + } + + public var authInteractor: AuthInteractorProtocol { + get { invocations.append(.p_authInteractor_get); return __p_authInteractor ?? givenGetterValue(.p_authInteractor_get, "WebviewCookiesUpdateProtocolMock - stub value for authInteractor was not defined") } + } + private var __p_authInteractor: (AuthInteractorProtocol)? + + public var cookiesReady: Bool { + get { invocations.append(.p_cookiesReady_get); return __p_cookiesReady ?? givenGetterValue(.p_cookiesReady_get, "WebviewCookiesUpdateProtocolMock - stub value for cookiesReady was not defined") } + set { invocations.append(.p_cookiesReady_set(.value(newValue))); __p_cookiesReady = newValue } + } + private var __p_cookiesReady: (Bool)? + + public var updatingCookies: Bool { + get { invocations.append(.p_updatingCookies_get); return __p_updatingCookies ?? givenGetterValue(.p_updatingCookies_get, "WebviewCookiesUpdateProtocolMock - stub value for updatingCookies was not defined") } + set { invocations.append(.p_updatingCookies_set(.value(newValue))); __p_updatingCookies = newValue } + } + private var __p_updatingCookies: (Bool)? + + public var errorMessage: String? { + get { invocations.append(.p_errorMessage_get); return __p_errorMessage ?? optionalGivenGetterValue(.p_errorMessage_get, "WebviewCookiesUpdateProtocolMock - stub value for errorMessage was not defined") } + set { invocations.append(.p_errorMessage_set(.value(newValue))); __p_errorMessage = newValue } + } + private var __p_errorMessage: (String)? + + + + + + open func updateCookies(force: Bool, retryCount: Int) { + addInvocation(.m_updateCookies__force_forceretryCount_retryCount(Parameter.value(`force`), Parameter.value(`retryCount`))) + let perform = methodPerformValue(.m_updateCookies__force_forceretryCount_retryCount(Parameter.value(`force`), Parameter.value(`retryCount`))) as? (Bool, Int) -> Void + perform?(`force`, `retryCount`) + } + + + fileprivate enum MethodType { + case m_updateCookies__force_forceretryCount_retryCount(Parameter, Parameter) + case p_authInteractor_get + case p_cookiesReady_get + case p_cookiesReady_set(Parameter) + case p_updatingCookies_get + case p_updatingCookies_set(Parameter) + case p_errorMessage_get + case p_errorMessage_set(Parameter) + + static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { + switch (lhs, rhs) { + case (.m_updateCookies__force_forceretryCount_retryCount(let lhsForce, let lhsRetrycount), .m_updateCookies__force_forceretryCount_retryCount(let rhsForce, let rhsRetrycount)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsForce, rhs: rhsForce, with: matcher), lhsForce, rhsForce, "force")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsRetrycount, rhs: rhsRetrycount, with: matcher), lhsRetrycount, rhsRetrycount, "retryCount")) + return Matcher.ComparisonResult(results) + case (.p_authInteractor_get,.p_authInteractor_get): return Matcher.ComparisonResult.match + case (.p_cookiesReady_get,.p_cookiesReady_get): return Matcher.ComparisonResult.match + case (.p_cookiesReady_set(let left),.p_cookiesReady_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_updatingCookies_get,.p_updatingCookies_get): return Matcher.ComparisonResult.match + case (.p_updatingCookies_set(let left),.p_updatingCookies_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_errorMessage_get,.p_errorMessage_get): return Matcher.ComparisonResult.match + case (.p_errorMessage_set(let left),.p_errorMessage_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + default: return .none + } + } + + func intValue() -> Int { + switch self { + case let .m_updateCookies__force_forceretryCount_retryCount(p0, p1): return p0.intValue + p1.intValue + case .p_authInteractor_get: return 0 + case .p_cookiesReady_get: return 0 + case .p_cookiesReady_set(let newValue): return newValue.intValue + case .p_updatingCookies_get: return 0 + case .p_updatingCookies_set(let newValue): return newValue.intValue + case .p_errorMessage_get: return 0 + case .p_errorMessage_set(let newValue): return newValue.intValue + } + } + func assertionName() -> String { + switch self { + case .m_updateCookies__force_forceretryCount_retryCount: return ".updateCookies(force:retryCount:)" + case .p_authInteractor_get: return "[get] .authInteractor" + case .p_cookiesReady_get: return "[get] .cookiesReady" + case .p_cookiesReady_set: return "[set] .cookiesReady" + case .p_updatingCookies_get: return "[get] .updatingCookies" + case .p_updatingCookies_set: return "[set] .updatingCookies" + case .p_errorMessage_get: return "[get] .errorMessage" + case .p_errorMessage_set: return "[set] .errorMessage" + } + } + } + + open class Given: StubbedMethod { + fileprivate var method: MethodType + + private init(method: MethodType, products: [StubProduct]) { + self.method = method + super.init(products) + } + + public static func authInteractor(getter defaultValue: AuthInteractorProtocol...) -> PropertyStub { + return Given(method: .p_authInteractor_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func cookiesReady(getter defaultValue: Bool...) -> PropertyStub { + return Given(method: .p_cookiesReady_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func updatingCookies(getter defaultValue: Bool...) -> PropertyStub { + return Given(method: .p_updatingCookies_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func errorMessage(getter defaultValue: String?...) -> PropertyStub { + return Given(method: .p_errorMessage_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + + } + + public struct Verify { + fileprivate var method: MethodType + + public static func updateCookies(force: Parameter, retryCount: Parameter) -> Verify { return Verify(method: .m_updateCookies__force_forceretryCount_retryCount(`force`, `retryCount`))} + public static var authInteractor: Verify { return Verify(method: .p_authInteractor_get) } + public static var cookiesReady: Verify { return Verify(method: .p_cookiesReady_get) } + public static func cookiesReady(set newValue: Parameter) -> Verify { return Verify(method: .p_cookiesReady_set(newValue)) } + public static var updatingCookies: Verify { return Verify(method: .p_updatingCookies_get) } + public static func updatingCookies(set newValue: Parameter) -> Verify { return Verify(method: .p_updatingCookies_set(newValue)) } + public static var errorMessage: Verify { return Verify(method: .p_errorMessage_get) } + public static func errorMessage(set newValue: Parameter) -> Verify { return Verify(method: .p_errorMessage_set(newValue)) } + } + + public struct Perform { + fileprivate var method: MethodType + var performs: Any + + public static func updateCookies(force: Parameter, retryCount: Parameter, perform: @escaping (Bool, Int) -> Void) -> Perform { + return Perform(method: .m_updateCookies__force_forceretryCount_retryCount(`force`, `retryCount`), performs: perform) + } + } + + public func given(_ method: Given) { + methodReturnValues.append(method) + } + + public func perform(_ method: Perform) { + methodPerformValues.append(method) + methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } + } + + public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { + let fullMatches = matchingCalls(method, file: file, line: line) + let success = count.matches(fullMatches) + let assertionName = method.method.assertionName() + let feedback: String = { + guard !success else { return "" } + return Utils.closestCallsMessage( + for: self.invocations.map { invocation in + matcher.set(file: file, line: line) + defer { matcher.clearFileAndLine() } + return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) + }, + name: assertionName + ) + }() + MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) + } + + private func addInvocation(_ call: MethodType) { + self.queue.sync { invocations.append(call) } + } + private func methodReturnValue(_ method: MethodType) throws -> StubProduct { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) + let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) + guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } + return product + } + private func methodPerformValue(_ method: MethodType) -> Any? { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } + return matched?.performs + } + private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { + matcher.set(file: file ?? self.file, line: line ?? self.line) + defer { matcher.clearFileAndLine() } + return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } + } + private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { + return matchingCalls(method.method, file: file, line: line).count + } + private func givenGetterValue(_ method: MethodType, _ message: String) -> T { + do { + return try methodReturnValue(method).casted() + } catch { + onFatalFailure(message) + Failure(message) + } + } + private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { + do { + return try methodReturnValue(method).casted() + } catch { + return nil + } + } + private func onFatalFailure(_ message: String) { + guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully + SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) + } +} + diff --git a/Discussion/DiscussionTests/DiscussionMock.generated.swift b/Discussion/DiscussionTests/DiscussionMock.generated.swift index fad4812d4..101137b24 100644 --- a/Discussion/DiscussionTests/DiscussionMock.generated.swift +++ b/Discussion/DiscussionTests/DiscussionMock.generated.swift @@ -3184,3 +3184,249 @@ open class DownloadManagerProtocolMock: DownloadManagerProtocol, Mock { } } +// MARK: - WebviewCookiesUpdateProtocol + +open class WebviewCookiesUpdateProtocolMock: WebviewCookiesUpdateProtocol, Mock { + public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { + SwiftyMockyTestObserver.setup() + self.sequencingPolicy = sequencingPolicy + self.stubbingPolicy = stubbingPolicy + self.file = file + self.line = line + } + + var matcher: Matcher = Matcher.default + var stubbingPolicy: StubbingPolicy = .wrap + var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst + + private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) + private var invocations: [MethodType] = [] + private var methodReturnValues: [Given] = [] + private var methodPerformValues: [Perform] = [] + private var file: StaticString? + private var line: UInt? + + public typealias PropertyStub = Given + public typealias MethodStub = Given + public typealias SubscriptStub = Given + + /// Convenience method - call setupMock() to extend debug information when failure occurs + public func setupMock(file: StaticString = #file, line: UInt = #line) { + self.file = file + self.line = line + } + + /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals + public func resetMock(_ scopes: MockScope...) { + let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes + if scopes.contains(.invocation) { invocations = [] } + if scopes.contains(.given) { methodReturnValues = [] } + if scopes.contains(.perform) { methodPerformValues = [] } + } + + public var authInteractor: AuthInteractorProtocol { + get { invocations.append(.p_authInteractor_get); return __p_authInteractor ?? givenGetterValue(.p_authInteractor_get, "WebviewCookiesUpdateProtocolMock - stub value for authInteractor was not defined") } + } + private var __p_authInteractor: (AuthInteractorProtocol)? + + public var cookiesReady: Bool { + get { invocations.append(.p_cookiesReady_get); return __p_cookiesReady ?? givenGetterValue(.p_cookiesReady_get, "WebviewCookiesUpdateProtocolMock - stub value for cookiesReady was not defined") } + set { invocations.append(.p_cookiesReady_set(.value(newValue))); __p_cookiesReady = newValue } + } + private var __p_cookiesReady: (Bool)? + + public var updatingCookies: Bool { + get { invocations.append(.p_updatingCookies_get); return __p_updatingCookies ?? givenGetterValue(.p_updatingCookies_get, "WebviewCookiesUpdateProtocolMock - stub value for updatingCookies was not defined") } + set { invocations.append(.p_updatingCookies_set(.value(newValue))); __p_updatingCookies = newValue } + } + private var __p_updatingCookies: (Bool)? + + public var errorMessage: String? { + get { invocations.append(.p_errorMessage_get); return __p_errorMessage ?? optionalGivenGetterValue(.p_errorMessage_get, "WebviewCookiesUpdateProtocolMock - stub value for errorMessage was not defined") } + set { invocations.append(.p_errorMessage_set(.value(newValue))); __p_errorMessage = newValue } + } + private var __p_errorMessage: (String)? + + + + + + open func updateCookies(force: Bool, retryCount: Int) { + addInvocation(.m_updateCookies__force_forceretryCount_retryCount(Parameter.value(`force`), Parameter.value(`retryCount`))) + let perform = methodPerformValue(.m_updateCookies__force_forceretryCount_retryCount(Parameter.value(`force`), Parameter.value(`retryCount`))) as? (Bool, Int) -> Void + perform?(`force`, `retryCount`) + } + + + fileprivate enum MethodType { + case m_updateCookies__force_forceretryCount_retryCount(Parameter, Parameter) + case p_authInteractor_get + case p_cookiesReady_get + case p_cookiesReady_set(Parameter) + case p_updatingCookies_get + case p_updatingCookies_set(Parameter) + case p_errorMessage_get + case p_errorMessage_set(Parameter) + + static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { + switch (lhs, rhs) { + case (.m_updateCookies__force_forceretryCount_retryCount(let lhsForce, let lhsRetrycount), .m_updateCookies__force_forceretryCount_retryCount(let rhsForce, let rhsRetrycount)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsForce, rhs: rhsForce, with: matcher), lhsForce, rhsForce, "force")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsRetrycount, rhs: rhsRetrycount, with: matcher), lhsRetrycount, rhsRetrycount, "retryCount")) + return Matcher.ComparisonResult(results) + case (.p_authInteractor_get,.p_authInteractor_get): return Matcher.ComparisonResult.match + case (.p_cookiesReady_get,.p_cookiesReady_get): return Matcher.ComparisonResult.match + case (.p_cookiesReady_set(let left),.p_cookiesReady_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_updatingCookies_get,.p_updatingCookies_get): return Matcher.ComparisonResult.match + case (.p_updatingCookies_set(let left),.p_updatingCookies_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_errorMessage_get,.p_errorMessage_get): return Matcher.ComparisonResult.match + case (.p_errorMessage_set(let left),.p_errorMessage_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + default: return .none + } + } + + func intValue() -> Int { + switch self { + case let .m_updateCookies__force_forceretryCount_retryCount(p0, p1): return p0.intValue + p1.intValue + case .p_authInteractor_get: return 0 + case .p_cookiesReady_get: return 0 + case .p_cookiesReady_set(let newValue): return newValue.intValue + case .p_updatingCookies_get: return 0 + case .p_updatingCookies_set(let newValue): return newValue.intValue + case .p_errorMessage_get: return 0 + case .p_errorMessage_set(let newValue): return newValue.intValue + } + } + func assertionName() -> String { + switch self { + case .m_updateCookies__force_forceretryCount_retryCount: return ".updateCookies(force:retryCount:)" + case .p_authInteractor_get: return "[get] .authInteractor" + case .p_cookiesReady_get: return "[get] .cookiesReady" + case .p_cookiesReady_set: return "[set] .cookiesReady" + case .p_updatingCookies_get: return "[get] .updatingCookies" + case .p_updatingCookies_set: return "[set] .updatingCookies" + case .p_errorMessage_get: return "[get] .errorMessage" + case .p_errorMessage_set: return "[set] .errorMessage" + } + } + } + + open class Given: StubbedMethod { + fileprivate var method: MethodType + + private init(method: MethodType, products: [StubProduct]) { + self.method = method + super.init(products) + } + + public static func authInteractor(getter defaultValue: AuthInteractorProtocol...) -> PropertyStub { + return Given(method: .p_authInteractor_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func cookiesReady(getter defaultValue: Bool...) -> PropertyStub { + return Given(method: .p_cookiesReady_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func updatingCookies(getter defaultValue: Bool...) -> PropertyStub { + return Given(method: .p_updatingCookies_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func errorMessage(getter defaultValue: String?...) -> PropertyStub { + return Given(method: .p_errorMessage_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + + } + + public struct Verify { + fileprivate var method: MethodType + + public static func updateCookies(force: Parameter, retryCount: Parameter) -> Verify { return Verify(method: .m_updateCookies__force_forceretryCount_retryCount(`force`, `retryCount`))} + public static var authInteractor: Verify { return Verify(method: .p_authInteractor_get) } + public static var cookiesReady: Verify { return Verify(method: .p_cookiesReady_get) } + public static func cookiesReady(set newValue: Parameter) -> Verify { return Verify(method: .p_cookiesReady_set(newValue)) } + public static var updatingCookies: Verify { return Verify(method: .p_updatingCookies_get) } + public static func updatingCookies(set newValue: Parameter) -> Verify { return Verify(method: .p_updatingCookies_set(newValue)) } + public static var errorMessage: Verify { return Verify(method: .p_errorMessage_get) } + public static func errorMessage(set newValue: Parameter) -> Verify { return Verify(method: .p_errorMessage_set(newValue)) } + } + + public struct Perform { + fileprivate var method: MethodType + var performs: Any + + public static func updateCookies(force: Parameter, retryCount: Parameter, perform: @escaping (Bool, Int) -> Void) -> Perform { + return Perform(method: .m_updateCookies__force_forceretryCount_retryCount(`force`, `retryCount`), performs: perform) + } + } + + public func given(_ method: Given) { + methodReturnValues.append(method) + } + + public func perform(_ method: Perform) { + methodPerformValues.append(method) + methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } + } + + public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { + let fullMatches = matchingCalls(method, file: file, line: line) + let success = count.matches(fullMatches) + let assertionName = method.method.assertionName() + let feedback: String = { + guard !success else { return "" } + return Utils.closestCallsMessage( + for: self.invocations.map { invocation in + matcher.set(file: file, line: line) + defer { matcher.clearFileAndLine() } + return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) + }, + name: assertionName + ) + }() + MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) + } + + private func addInvocation(_ call: MethodType) { + self.queue.sync { invocations.append(call) } + } + private func methodReturnValue(_ method: MethodType) throws -> StubProduct { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) + let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) + guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } + return product + } + private func methodPerformValue(_ method: MethodType) -> Any? { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } + return matched?.performs + } + private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { + matcher.set(file: file ?? self.file, line: line ?? self.line) + defer { matcher.clearFileAndLine() } + return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } + } + private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { + return matchingCalls(method.method, file: file, line: line).count + } + private func givenGetterValue(_ method: MethodType, _ message: String) -> T { + do { + return try methodReturnValue(method).casted() + } catch { + onFatalFailure(message) + Failure(message) + } + } + private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { + do { + return try methodReturnValue(method).casted() + } catch { + return nil + } + } + private func onFatalFailure(_ message: String) { + guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully + SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) + } +} + diff --git a/Profile/ProfileTests/ProfileMock.generated.swift b/Profile/ProfileTests/ProfileMock.generated.swift index 047a50d8d..75370f29d 100644 --- a/Profile/ProfileTests/ProfileMock.generated.swift +++ b/Profile/ProfileTests/ProfileMock.generated.swift @@ -3086,3 +3086,249 @@ open class ProfileRouterMock: ProfileRouter, Mock { } } +// MARK: - WebviewCookiesUpdateProtocol + +open class WebviewCookiesUpdateProtocolMock: WebviewCookiesUpdateProtocol, Mock { + public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { + SwiftyMockyTestObserver.setup() + self.sequencingPolicy = sequencingPolicy + self.stubbingPolicy = stubbingPolicy + self.file = file + self.line = line + } + + var matcher: Matcher = Matcher.default + var stubbingPolicy: StubbingPolicy = .wrap + var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst + + private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) + private var invocations: [MethodType] = [] + private var methodReturnValues: [Given] = [] + private var methodPerformValues: [Perform] = [] + private var file: StaticString? + private var line: UInt? + + public typealias PropertyStub = Given + public typealias MethodStub = Given + public typealias SubscriptStub = Given + + /// Convenience method - call setupMock() to extend debug information when failure occurs + public func setupMock(file: StaticString = #file, line: UInt = #line) { + self.file = file + self.line = line + } + + /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals + public func resetMock(_ scopes: MockScope...) { + let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes + if scopes.contains(.invocation) { invocations = [] } + if scopes.contains(.given) { methodReturnValues = [] } + if scopes.contains(.perform) { methodPerformValues = [] } + } + + public var authInteractor: AuthInteractorProtocol { + get { invocations.append(.p_authInteractor_get); return __p_authInteractor ?? givenGetterValue(.p_authInteractor_get, "WebviewCookiesUpdateProtocolMock - stub value for authInteractor was not defined") } + } + private var __p_authInteractor: (AuthInteractorProtocol)? + + public var cookiesReady: Bool { + get { invocations.append(.p_cookiesReady_get); return __p_cookiesReady ?? givenGetterValue(.p_cookiesReady_get, "WebviewCookiesUpdateProtocolMock - stub value for cookiesReady was not defined") } + set { invocations.append(.p_cookiesReady_set(.value(newValue))); __p_cookiesReady = newValue } + } + private var __p_cookiesReady: (Bool)? + + public var updatingCookies: Bool { + get { invocations.append(.p_updatingCookies_get); return __p_updatingCookies ?? givenGetterValue(.p_updatingCookies_get, "WebviewCookiesUpdateProtocolMock - stub value for updatingCookies was not defined") } + set { invocations.append(.p_updatingCookies_set(.value(newValue))); __p_updatingCookies = newValue } + } + private var __p_updatingCookies: (Bool)? + + public var errorMessage: String? { + get { invocations.append(.p_errorMessage_get); return __p_errorMessage ?? optionalGivenGetterValue(.p_errorMessage_get, "WebviewCookiesUpdateProtocolMock - stub value for errorMessage was not defined") } + set { invocations.append(.p_errorMessage_set(.value(newValue))); __p_errorMessage = newValue } + } + private var __p_errorMessage: (String)? + + + + + + open func updateCookies(force: Bool, retryCount: Int) { + addInvocation(.m_updateCookies__force_forceretryCount_retryCount(Parameter.value(`force`), Parameter.value(`retryCount`))) + let perform = methodPerformValue(.m_updateCookies__force_forceretryCount_retryCount(Parameter.value(`force`), Parameter.value(`retryCount`))) as? (Bool, Int) -> Void + perform?(`force`, `retryCount`) + } + + + fileprivate enum MethodType { + case m_updateCookies__force_forceretryCount_retryCount(Parameter, Parameter) + case p_authInteractor_get + case p_cookiesReady_get + case p_cookiesReady_set(Parameter) + case p_updatingCookies_get + case p_updatingCookies_set(Parameter) + case p_errorMessage_get + case p_errorMessage_set(Parameter) + + static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { + switch (lhs, rhs) { + case (.m_updateCookies__force_forceretryCount_retryCount(let lhsForce, let lhsRetrycount), .m_updateCookies__force_forceretryCount_retryCount(let rhsForce, let rhsRetrycount)): + var results: [Matcher.ParameterComparisonResult] = [] + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsForce, rhs: rhsForce, with: matcher), lhsForce, rhsForce, "force")) + results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsRetrycount, rhs: rhsRetrycount, with: matcher), lhsRetrycount, rhsRetrycount, "retryCount")) + return Matcher.ComparisonResult(results) + case (.p_authInteractor_get,.p_authInteractor_get): return Matcher.ComparisonResult.match + case (.p_cookiesReady_get,.p_cookiesReady_get): return Matcher.ComparisonResult.match + case (.p_cookiesReady_set(let left),.p_cookiesReady_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_updatingCookies_get,.p_updatingCookies_get): return Matcher.ComparisonResult.match + case (.p_updatingCookies_set(let left),.p_updatingCookies_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + case (.p_errorMessage_get,.p_errorMessage_get): return Matcher.ComparisonResult.match + case (.p_errorMessage_set(let left),.p_errorMessage_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) + default: return .none + } + } + + func intValue() -> Int { + switch self { + case let .m_updateCookies__force_forceretryCount_retryCount(p0, p1): return p0.intValue + p1.intValue + case .p_authInteractor_get: return 0 + case .p_cookiesReady_get: return 0 + case .p_cookiesReady_set(let newValue): return newValue.intValue + case .p_updatingCookies_get: return 0 + case .p_updatingCookies_set(let newValue): return newValue.intValue + case .p_errorMessage_get: return 0 + case .p_errorMessage_set(let newValue): return newValue.intValue + } + } + func assertionName() -> String { + switch self { + case .m_updateCookies__force_forceretryCount_retryCount: return ".updateCookies(force:retryCount:)" + case .p_authInteractor_get: return "[get] .authInteractor" + case .p_cookiesReady_get: return "[get] .cookiesReady" + case .p_cookiesReady_set: return "[set] .cookiesReady" + case .p_updatingCookies_get: return "[get] .updatingCookies" + case .p_updatingCookies_set: return "[set] .updatingCookies" + case .p_errorMessage_get: return "[get] .errorMessage" + case .p_errorMessage_set: return "[set] .errorMessage" + } + } + } + + open class Given: StubbedMethod { + fileprivate var method: MethodType + + private init(method: MethodType, products: [StubProduct]) { + self.method = method + super.init(products) + } + + public static func authInteractor(getter defaultValue: AuthInteractorProtocol...) -> PropertyStub { + return Given(method: .p_authInteractor_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func cookiesReady(getter defaultValue: Bool...) -> PropertyStub { + return Given(method: .p_cookiesReady_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func updatingCookies(getter defaultValue: Bool...) -> PropertyStub { + return Given(method: .p_updatingCookies_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + public static func errorMessage(getter defaultValue: String?...) -> PropertyStub { + return Given(method: .p_errorMessage_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) + } + + } + + public struct Verify { + fileprivate var method: MethodType + + public static func updateCookies(force: Parameter, retryCount: Parameter) -> Verify { return Verify(method: .m_updateCookies__force_forceretryCount_retryCount(`force`, `retryCount`))} + public static var authInteractor: Verify { return Verify(method: .p_authInteractor_get) } + public static var cookiesReady: Verify { return Verify(method: .p_cookiesReady_get) } + public static func cookiesReady(set newValue: Parameter) -> Verify { return Verify(method: .p_cookiesReady_set(newValue)) } + public static var updatingCookies: Verify { return Verify(method: .p_updatingCookies_get) } + public static func updatingCookies(set newValue: Parameter) -> Verify { return Verify(method: .p_updatingCookies_set(newValue)) } + public static var errorMessage: Verify { return Verify(method: .p_errorMessage_get) } + public static func errorMessage(set newValue: Parameter) -> Verify { return Verify(method: .p_errorMessage_set(newValue)) } + } + + public struct Perform { + fileprivate var method: MethodType + var performs: Any + + public static func updateCookies(force: Parameter, retryCount: Parameter, perform: @escaping (Bool, Int) -> Void) -> Perform { + return Perform(method: .m_updateCookies__force_forceretryCount_retryCount(`force`, `retryCount`), performs: perform) + } + } + + public func given(_ method: Given) { + methodReturnValues.append(method) + } + + public func perform(_ method: Perform) { + methodPerformValues.append(method) + methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } + } + + public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { + let fullMatches = matchingCalls(method, file: file, line: line) + let success = count.matches(fullMatches) + let assertionName = method.method.assertionName() + let feedback: String = { + guard !success else { return "" } + return Utils.closestCallsMessage( + for: self.invocations.map { invocation in + matcher.set(file: file, line: line) + defer { matcher.clearFileAndLine() } + return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) + }, + name: assertionName + ) + }() + MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) + } + + private func addInvocation(_ call: MethodType) { + self.queue.sync { invocations.append(call) } + } + private func methodReturnValue(_ method: MethodType) throws -> StubProduct { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) + let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) + guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } + return product + } + private func methodPerformValue(_ method: MethodType) -> Any? { + matcher.set(file: self.file, line: self.line) + defer { matcher.clearFileAndLine() } + let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } + return matched?.performs + } + private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { + matcher.set(file: file ?? self.file, line: line ?? self.line) + defer { matcher.clearFileAndLine() } + return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } + } + private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { + return matchingCalls(method.method, file: file, line: line).count + } + private func givenGetterValue(_ method: MethodType, _ message: String) -> T { + do { + return try methodReturnValue(method).casted() + } catch { + onFatalFailure(message) + Failure(message) + } + } + private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { + do { + return try methodReturnValue(method).casted() + } catch { + return nil + } + } + private func onFatalFailure(_ message: String) { + guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully + SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) + } +} + From f40b7f27bc2c75307e762bcf34f2ef5b3c10402c Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Thu, 22 Feb 2024 12:53:04 +0100 Subject: [PATCH 20/21] refactor: address review feedback, added test for segment config --- .../Configuration/Config/SegmentConfig.swift | 2 +- .../CoreTests/Configuration/ConfigTests.swift | 11 ++++++++ OpenEdX.xcodeproj/project.pbxproj | 28 +++++++++---------- OpenEdX/DI/AppAssembly.swift | 8 +++--- .../AnalyticsManager/AnalyticsManager.swift | 8 +++--- .../FirebaseAnalyticsService.swift} | 4 +-- .../Providers/BrazeProvider.swift | 4 +-- .../SegmentAnalyticsService.swift} | 6 ++-- 8 files changed, 41 insertions(+), 30 deletions(-) rename OpenEdX/Managers/{FirebaseAnalyticsManager/FirebaseAnalyticsManager.swift => FirebaseAnalyticsService/FirebaseAnalyticsService.swift} (86%) rename OpenEdX/Managers/{SegmentAnalyticsManager/SegmentAnalyticsManager.swift => SegmentAnalyticsService/SegmentAnalyticsService.swift} (87%) diff --git a/Core/Core/Configuration/Config/SegmentConfig.swift b/Core/Core/Configuration/Config/SegmentConfig.swift index 37ed53cc9..937a78015 100644 --- a/Core/Core/Configuration/Config/SegmentConfig.swift +++ b/Core/Core/Configuration/Config/SegmentConfig.swift @@ -18,8 +18,8 @@ public final class SegmentConfig: NSObject { init(dictionary: [String: AnyObject]) { super.init() - enabled = dictionary[SegmentKeys.enabled] as? Bool == true writeKey = dictionary[SegmentKeys.writeKey] as? String ?? "" + enabled = dictionary[SegmentKeys.enabled] as? Bool == true && !writeKey.isEmpty } } diff --git a/Core/CoreTests/Configuration/ConfigTests.swift b/Core/CoreTests/Configuration/ConfigTests.swift index 21ab17ddf..35a90c2b7 100644 --- a/Core/CoreTests/Configuration/ConfigTests.swift +++ b/Core/CoreTests/Configuration/ConfigTests.swift @@ -60,6 +60,10 @@ class ConfigTests: XCTestCase { "BRANCH": [ "ENABLED": true, "KEY": "testBranchKey" + ], + "SEGMENT_IO": [ + "ENABLED": true, + "SEGMENT_IO_WRITE_KEY": "testSegmentKey" ] ] @@ -136,4 +140,11 @@ class ConfigTests: XCTestCase { XCTAssertTrue(config.branch.enabled) XCTAssertEqual(config.branch.key, "testBranchKey") } + + func testSegmentConfigInitialization() { + let config = Config(properties: properties) + + XCTAssertTrue(config.segment.enabled) + XCTAssertEqual(config.segment.writeKey, "testSegmentKey") + } } diff --git a/OpenEdX.xcodeproj/project.pbxproj b/OpenEdX.xcodeproj/project.pbxproj index dbbde7246..a7084fabf 100644 --- a/OpenEdX.xcodeproj/project.pbxproj +++ b/OpenEdX.xcodeproj/project.pbxproj @@ -57,9 +57,9 @@ A59568952B61630500ED4F90 /* DeepLinkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568942B61630500ED4F90 /* DeepLinkManager.swift */; }; A59568972B61653700ED4F90 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568962B61653700ED4F90 /* DeepLink.swift */; }; A59568992B616D9400ED4F90 /* PushLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59568982B616D9400ED4F90 /* PushLink.swift */; }; - A59702292B83C87900CA064C /* FirebaseAnalyticsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59702282B83C87900CA064C /* FirebaseAnalyticsManager.swift */; }; + A59702292B83C87900CA064C /* FirebaseAnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59702282B83C87900CA064C /* FirebaseAnalyticsService.swift */; }; A5BD3E302B83B0F7006A8983 /* SegmentFirebase in Frameworks */ = {isa = PBXBuildFile; productRef = A5BD3E2F2B83B0F7006A8983 /* SegmentFirebase */; }; - A5C10D8F2B861A70008E864D /* SegmentAnalyticsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5C10D8E2B861A70008E864D /* SegmentAnalyticsManager.swift */; }; + A5C10D8F2B861A70008E864D /* SegmentAnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5C10D8E2B861A70008E864D /* SegmentAnalyticsService.swift */; }; BA3042792B1F7147009B64B7 /* MSAL in Frameworks */ = {isa = PBXBuildFile; productRef = BA3042782B1F7147009B64B7 /* MSAL */; }; E0D6E6A32B1626B10089F9C9 /* Theme.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0D6E6A22B1626B10089F9C9 /* Theme.framework */; }; E0D6E6A42B1626D60089F9C9 /* Theme.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E0D6E6A22B1626B10089F9C9 /* Theme.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -139,8 +139,8 @@ A59568942B61630500ED4F90 /* DeepLinkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkManager.swift; sourceTree = ""; }; A59568962B61653700ED4F90 /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = ""; }; A59568982B616D9400ED4F90 /* PushLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushLink.swift; sourceTree = ""; }; - A59702282B83C87900CA064C /* FirebaseAnalyticsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseAnalyticsManager.swift; sourceTree = ""; }; - A5C10D8E2B861A70008E864D /* SegmentAnalyticsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentAnalyticsManager.swift; sourceTree = ""; }; + A59702282B83C87900CA064C /* FirebaseAnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseAnalyticsService.swift; sourceTree = ""; }; + A5C10D8E2B861A70008E864D /* SegmentAnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentAnalyticsService.swift; sourceTree = ""; }; A89AD827F52CF6A6B903606E /* Pods-App-OpenEdX.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-OpenEdX.releasestage.xcconfig"; path = "Target Support Files/Pods-App-OpenEdX/Pods-App-OpenEdX.releasestage.xcconfig"; sourceTree = ""; }; BB08ACD2CCA33D6DDDDD31B4 /* Pods-App-OpenEdX.releasedev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-OpenEdX.releasedev.xcconfig"; path = "Target Support Files/Pods-App-OpenEdX/Pods-App-OpenEdX.releasedev.xcconfig"; sourceTree = ""; }; E0D6E6A22B1626B10089F9C9 /* Theme.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Theme.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -293,8 +293,8 @@ A59568932B6162E400ED4F90 /* DeepLinkManager */, A50066872B613E4B0024680B /* PushNotificationsManager */, A50066892B613E990024680B /* AnalyticsManager */, - A59702272B83C84800CA064C /* FirebaseAnalyticsManager */, - A5C10D8D2B861A56008E864D /* SegmentAnalyticsManager */, + A59702272B83C84800CA064C /* FirebaseAnalyticsService */, + A5C10D8D2B861A56008E864D /* SegmentAnalyticsService */, ); path = Managers; sourceTree = ""; @@ -345,20 +345,20 @@ path = Link; sourceTree = ""; }; - A59702272B83C84800CA064C /* FirebaseAnalyticsManager */ = { + A59702272B83C84800CA064C /* FirebaseAnalyticsService */ = { isa = PBXGroup; children = ( - A59702282B83C87900CA064C /* FirebaseAnalyticsManager.swift */, + A59702282B83C87900CA064C /* FirebaseAnalyticsService.swift */, ); - path = FirebaseAnalyticsManager; + path = FirebaseAnalyticsService; sourceTree = ""; }; - A5C10D8D2B861A56008E864D /* SegmentAnalyticsManager */ = { + A5C10D8D2B861A56008E864D /* SegmentAnalyticsService */ = { isa = PBXGroup; children = ( - A5C10D8E2B861A70008E864D /* SegmentAnalyticsManager.swift */, + A5C10D8E2B861A70008E864D /* SegmentAnalyticsService.swift */, ); - path = SegmentAnalyticsManager; + path = SegmentAnalyticsService; sourceTree = ""; }; A5F46FD02B692B140003EEEF /* Services */ = { @@ -560,7 +560,7 @@ 0770DE1E28D084E8006D8A5D /* AppAssembly.swift in Sources */, A5462D9C2B864AE0003B96A5 /* BranchService.swift in Sources */, A50066932B614DCD0024680B /* FCMListener.swift in Sources */, - A59702292B83C87900CA064C /* FirebaseAnalyticsManager.swift in Sources */, + A59702292B83C87900CA064C /* FirebaseAnalyticsService.swift in Sources */, A500668D2B6143000024680B /* FCMProvider.swift in Sources */, 025AD4AC2A6FB95C00AB8FA7 /* DatabaseManager.swift in Sources */, 024E69202AEFC3FB00FA0B59 /* MainScreenViewModel.swift in Sources */, @@ -571,7 +571,7 @@ A50066952B614DEF0024680B /* BrazeListener.swift in Sources */, 071009C928D1DB3F00344290 /* ScreenAssembly.swift in Sources */, A59568972B61653700ED4F90 /* DeepLink.swift in Sources */, - A5C10D8F2B861A70008E864D /* SegmentAnalyticsManager.swift in Sources */, + A5C10D8F2B861A70008E864D /* SegmentAnalyticsService.swift in Sources */, A59568992B616D9400ED4F90 /* PushLink.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/OpenEdX/DI/AppAssembly.swift b/OpenEdX/DI/AppAssembly.swift index ac404b136..2c65937c8 100644 --- a/OpenEdX/DI/AppAssembly.swift +++ b/OpenEdX/DI/AppAssembly.swift @@ -176,14 +176,14 @@ class AppAssembly: Assembly { ) }.inObjectScope(.container) - container.register(SegmentAnalyticsManager.self) { r in - SegmentAnalyticsManager( + container.register(SegmentAnalyticsService.self) { r in + SegmentAnalyticsService( config: r.resolve(ConfigProtocol.self)! ) }.inObjectScope(.container) - container.register(FirebaseAnalyticsManager.self) { r in - FirebaseAnalyticsManager( + container.register(FirebaseAnalyticsService.self) { r in + FirebaseAnalyticsService( config: r.resolve(ConfigProtocol.self)! ) }.inObjectScope(.container) diff --git a/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift b/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift index 105605f64..4cb616fc8 100644 --- a/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift +++ b/OpenEdX/Managers/AnalyticsManager/AnalyticsManager.swift @@ -39,14 +39,14 @@ class AnalyticsManager: AuthorizationAnalytics, var analyticsServices: [AnalyticsService] = [] // add Firebase Analytics Service if config.firebase.enabled && config.firebase.isAnalyticsSourceFirebase, - let firebaseManager = Container.shared.resolve(FirebaseAnalyticsManager.self) { - analyticsServices.append(firebaseManager) + let firebaseService = Container.shared.resolve(FirebaseAnalyticsService.self) { + analyticsServices.append(firebaseService) } // add Segment Analytics Service if config.segment.enabled, - let segmentManager = Container.shared.resolve(SegmentAnalyticsManager.self) { - analyticsServices.append(segmentManager) + let segmentService = Container.shared.resolve(SegmentAnalyticsService.self) { + analyticsServices.append(segmentService) } return analyticsServices } diff --git a/OpenEdX/Managers/FirebaseAnalyticsManager/FirebaseAnalyticsManager.swift b/OpenEdX/Managers/FirebaseAnalyticsService/FirebaseAnalyticsService.swift similarity index 86% rename from OpenEdX/Managers/FirebaseAnalyticsManager/FirebaseAnalyticsManager.swift rename to OpenEdX/Managers/FirebaseAnalyticsService/FirebaseAnalyticsService.swift index 6c1ac0343..f41df2555 100644 --- a/OpenEdX/Managers/FirebaseAnalyticsManager/FirebaseAnalyticsManager.swift +++ b/OpenEdX/Managers/FirebaseAnalyticsService/FirebaseAnalyticsService.swift @@ -1,5 +1,5 @@ // -// FirebaseAnalyticsManager.swift +// FirebaseAnalyticsService.swift // OpenEdX // // Created by Anton Yarmolenka on 19/02/2024. @@ -9,7 +9,7 @@ import Foundation import Firebase import Core -class FirebaseAnalyticsManager: AnalyticsService { +class FirebaseAnalyticsService: AnalyticsService { // Init manager public init(config: ConfigProtocol) { guard config.firebase.enabled && config.firebase.isAnalyticsSourceFirebase else { return } diff --git a/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift b/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift index acaddc0ba..9e45059e3 100644 --- a/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift +++ b/OpenEdX/Managers/PushNotificationsManager/Providers/BrazeProvider.swift @@ -11,8 +11,8 @@ import Swinject class BrazeProvider: PushNotificationsProvider { func didRegisterWithDeviceToken(deviceToken: Data) { - guard let segmentManager = Container.shared.resolve(SegmentAnalyticsManager.self) else { return } - segmentManager.analytics?.add( + guard let segmentService = Container.shared.resolve(SegmentAnalyticsService.self) else { return } + segmentService.analytics?.add( plugin: BrazeDestination( additionalConfiguration: { configuration in configuration.logger.level = .debug diff --git a/OpenEdX/Managers/SegmentAnalyticsManager/SegmentAnalyticsManager.swift b/OpenEdX/Managers/SegmentAnalyticsService/SegmentAnalyticsService.swift similarity index 87% rename from OpenEdX/Managers/SegmentAnalyticsManager/SegmentAnalyticsManager.swift rename to OpenEdX/Managers/SegmentAnalyticsService/SegmentAnalyticsService.swift index ba3012518..0d2315ec5 100644 --- a/OpenEdX/Managers/SegmentAnalyticsManager/SegmentAnalyticsManager.swift +++ b/OpenEdX/Managers/SegmentAnalyticsService/SegmentAnalyticsService.swift @@ -1,5 +1,5 @@ // -// SegmentAnalyticsManager.swift +// SegmentAnalyticsService.swift // OpenEdX // // Created by Anton Yarmolenka on 21/02/2024. @@ -10,7 +10,7 @@ import Core import Segment import SegmentFirebase -class SegmentAnalyticsManager: AnalyticsService { +class SegmentAnalyticsService: AnalyticsService { var analytics: Analytics? // Init manager @@ -21,7 +21,7 @@ class SegmentAnalyticsManager: AnalyticsService { .trackApplicationLifecycleEvents(true) .flushInterval(10) analytics = Analytics(configuration: configuration) - if config.firebase.isAnalyticsSourceSegment { + if config.firebase.enabled && config.firebase.isAnalyticsSourceSegment { analytics?.add(plugin: FirebaseDestination()) } } From fb220732bf3dd7360568524732c9579c864b94b6 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Mon, 26 Feb 2024 11:01:07 +0100 Subject: [PATCH 21/21] chore: auto-generated mocks --- .../AuthorizationMock.generated.swift | 246 ------------------ Course/CourseTests/CourseMock.generated.swift | 246 ------------------ .../DashboardMock.generated.swift | 246 ------------------ .../DiscoveryMock.generated.swift | 246 ------------------ .../DiscussionMock.generated.swift | 246 ------------------ 5 files changed, 1230 deletions(-) diff --git a/Authorization/AuthorizationTests/AuthorizationMock.generated.swift b/Authorization/AuthorizationTests/AuthorizationMock.generated.swift index 40d06692c..d1f8d0a71 100644 --- a/Authorization/AuthorizationTests/AuthorizationMock.generated.swift +++ b/Authorization/AuthorizationTests/AuthorizationMock.generated.swift @@ -2716,249 +2716,3 @@ open class WebviewCookiesUpdateProtocolMock: WebviewCookiesUpdateProtocol, Mock } } -// MARK: - WebviewCookiesUpdateProtocol - -open class WebviewCookiesUpdateProtocolMock: WebviewCookiesUpdateProtocol, Mock { - public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { - SwiftyMockyTestObserver.setup() - self.sequencingPolicy = sequencingPolicy - self.stubbingPolicy = stubbingPolicy - self.file = file - self.line = line - } - - var matcher: Matcher = Matcher.default - var stubbingPolicy: StubbingPolicy = .wrap - var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst - - private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) - private var invocations: [MethodType] = [] - private var methodReturnValues: [Given] = [] - private var methodPerformValues: [Perform] = [] - private var file: StaticString? - private var line: UInt? - - public typealias PropertyStub = Given - public typealias MethodStub = Given - public typealias SubscriptStub = Given - - /// Convenience method - call setupMock() to extend debug information when failure occurs - public func setupMock(file: StaticString = #file, line: UInt = #line) { - self.file = file - self.line = line - } - - /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals - public func resetMock(_ scopes: MockScope...) { - let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes - if scopes.contains(.invocation) { invocations = [] } - if scopes.contains(.given) { methodReturnValues = [] } - if scopes.contains(.perform) { methodPerformValues = [] } - } - - public var authInteractor: AuthInteractorProtocol { - get { invocations.append(.p_authInteractor_get); return __p_authInteractor ?? givenGetterValue(.p_authInteractor_get, "WebviewCookiesUpdateProtocolMock - stub value for authInteractor was not defined") } - } - private var __p_authInteractor: (AuthInteractorProtocol)? - - public var cookiesReady: Bool { - get { invocations.append(.p_cookiesReady_get); return __p_cookiesReady ?? givenGetterValue(.p_cookiesReady_get, "WebviewCookiesUpdateProtocolMock - stub value for cookiesReady was not defined") } - set { invocations.append(.p_cookiesReady_set(.value(newValue))); __p_cookiesReady = newValue } - } - private var __p_cookiesReady: (Bool)? - - public var updatingCookies: Bool { - get { invocations.append(.p_updatingCookies_get); return __p_updatingCookies ?? givenGetterValue(.p_updatingCookies_get, "WebviewCookiesUpdateProtocolMock - stub value for updatingCookies was not defined") } - set { invocations.append(.p_updatingCookies_set(.value(newValue))); __p_updatingCookies = newValue } - } - private var __p_updatingCookies: (Bool)? - - public var errorMessage: String? { - get { invocations.append(.p_errorMessage_get); return __p_errorMessage ?? optionalGivenGetterValue(.p_errorMessage_get, "WebviewCookiesUpdateProtocolMock - stub value for errorMessage was not defined") } - set { invocations.append(.p_errorMessage_set(.value(newValue))); __p_errorMessage = newValue } - } - private var __p_errorMessage: (String)? - - - - - - open func updateCookies(force: Bool, retryCount: Int) { - addInvocation(.m_updateCookies__force_forceretryCount_retryCount(Parameter.value(`force`), Parameter.value(`retryCount`))) - let perform = methodPerformValue(.m_updateCookies__force_forceretryCount_retryCount(Parameter.value(`force`), Parameter.value(`retryCount`))) as? (Bool, Int) -> Void - perform?(`force`, `retryCount`) - } - - - fileprivate enum MethodType { - case m_updateCookies__force_forceretryCount_retryCount(Parameter, Parameter) - case p_authInteractor_get - case p_cookiesReady_get - case p_cookiesReady_set(Parameter) - case p_updatingCookies_get - case p_updatingCookies_set(Parameter) - case p_errorMessage_get - case p_errorMessage_set(Parameter) - - static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { - switch (lhs, rhs) { - case (.m_updateCookies__force_forceretryCount_retryCount(let lhsForce, let lhsRetrycount), .m_updateCookies__force_forceretryCount_retryCount(let rhsForce, let rhsRetrycount)): - var results: [Matcher.ParameterComparisonResult] = [] - results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsForce, rhs: rhsForce, with: matcher), lhsForce, rhsForce, "force")) - results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsRetrycount, rhs: rhsRetrycount, with: matcher), lhsRetrycount, rhsRetrycount, "retryCount")) - return Matcher.ComparisonResult(results) - case (.p_authInteractor_get,.p_authInteractor_get): return Matcher.ComparisonResult.match - case (.p_cookiesReady_get,.p_cookiesReady_get): return Matcher.ComparisonResult.match - case (.p_cookiesReady_set(let left),.p_cookiesReady_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) - case (.p_updatingCookies_get,.p_updatingCookies_get): return Matcher.ComparisonResult.match - case (.p_updatingCookies_set(let left),.p_updatingCookies_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) - case (.p_errorMessage_get,.p_errorMessage_get): return Matcher.ComparisonResult.match - case (.p_errorMessage_set(let left),.p_errorMessage_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) - default: return .none - } - } - - func intValue() -> Int { - switch self { - case let .m_updateCookies__force_forceretryCount_retryCount(p0, p1): return p0.intValue + p1.intValue - case .p_authInteractor_get: return 0 - case .p_cookiesReady_get: return 0 - case .p_cookiesReady_set(let newValue): return newValue.intValue - case .p_updatingCookies_get: return 0 - case .p_updatingCookies_set(let newValue): return newValue.intValue - case .p_errorMessage_get: return 0 - case .p_errorMessage_set(let newValue): return newValue.intValue - } - } - func assertionName() -> String { - switch self { - case .m_updateCookies__force_forceretryCount_retryCount: return ".updateCookies(force:retryCount:)" - case .p_authInteractor_get: return "[get] .authInteractor" - case .p_cookiesReady_get: return "[get] .cookiesReady" - case .p_cookiesReady_set: return "[set] .cookiesReady" - case .p_updatingCookies_get: return "[get] .updatingCookies" - case .p_updatingCookies_set: return "[set] .updatingCookies" - case .p_errorMessage_get: return "[get] .errorMessage" - case .p_errorMessage_set: return "[set] .errorMessage" - } - } - } - - open class Given: StubbedMethod { - fileprivate var method: MethodType - - private init(method: MethodType, products: [StubProduct]) { - self.method = method - super.init(products) - } - - public static func authInteractor(getter defaultValue: AuthInteractorProtocol...) -> PropertyStub { - return Given(method: .p_authInteractor_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) - } - public static func cookiesReady(getter defaultValue: Bool...) -> PropertyStub { - return Given(method: .p_cookiesReady_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) - } - public static func updatingCookies(getter defaultValue: Bool...) -> PropertyStub { - return Given(method: .p_updatingCookies_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) - } - public static func errorMessage(getter defaultValue: String?...) -> PropertyStub { - return Given(method: .p_errorMessage_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) - } - - } - - public struct Verify { - fileprivate var method: MethodType - - public static func updateCookies(force: Parameter, retryCount: Parameter) -> Verify { return Verify(method: .m_updateCookies__force_forceretryCount_retryCount(`force`, `retryCount`))} - public static var authInteractor: Verify { return Verify(method: .p_authInteractor_get) } - public static var cookiesReady: Verify { return Verify(method: .p_cookiesReady_get) } - public static func cookiesReady(set newValue: Parameter) -> Verify { return Verify(method: .p_cookiesReady_set(newValue)) } - public static var updatingCookies: Verify { return Verify(method: .p_updatingCookies_get) } - public static func updatingCookies(set newValue: Parameter) -> Verify { return Verify(method: .p_updatingCookies_set(newValue)) } - public static var errorMessage: Verify { return Verify(method: .p_errorMessage_get) } - public static func errorMessage(set newValue: Parameter) -> Verify { return Verify(method: .p_errorMessage_set(newValue)) } - } - - public struct Perform { - fileprivate var method: MethodType - var performs: Any - - public static func updateCookies(force: Parameter, retryCount: Parameter, perform: @escaping (Bool, Int) -> Void) -> Perform { - return Perform(method: .m_updateCookies__force_forceretryCount_retryCount(`force`, `retryCount`), performs: perform) - } - } - - public func given(_ method: Given) { - methodReturnValues.append(method) - } - - public func perform(_ method: Perform) { - methodPerformValues.append(method) - methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } - } - - public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { - let fullMatches = matchingCalls(method, file: file, line: line) - let success = count.matches(fullMatches) - let assertionName = method.method.assertionName() - let feedback: String = { - guard !success else { return "" } - return Utils.closestCallsMessage( - for: self.invocations.map { invocation in - matcher.set(file: file, line: line) - defer { matcher.clearFileAndLine() } - return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) - }, - name: assertionName - ) - }() - MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) - } - - private func addInvocation(_ call: MethodType) { - self.queue.sync { invocations.append(call) } - } - private func methodReturnValue(_ method: MethodType) throws -> StubProduct { - matcher.set(file: self.file, line: self.line) - defer { matcher.clearFileAndLine() } - let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) - let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) - guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } - return product - } - private func methodPerformValue(_ method: MethodType) -> Any? { - matcher.set(file: self.file, line: self.line) - defer { matcher.clearFileAndLine() } - let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } - return matched?.performs - } - private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { - matcher.set(file: file ?? self.file, line: line ?? self.line) - defer { matcher.clearFileAndLine() } - return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } - } - private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { - return matchingCalls(method.method, file: file, line: line).count - } - private func givenGetterValue(_ method: MethodType, _ message: String) -> T { - do { - return try methodReturnValue(method).casted() - } catch { - onFatalFailure(message) - Failure(message) - } - } - private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { - do { - return try methodReturnValue(method).casted() - } catch { - return nil - } - } - private func onFatalFailure(_ message: String) { - guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully - SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) - } -} - diff --git a/Course/CourseTests/CourseMock.generated.swift b/Course/CourseTests/CourseMock.generated.swift index 17ce81c80..dabfab196 100644 --- a/Course/CourseTests/CourseMock.generated.swift +++ b/Course/CourseTests/CourseMock.generated.swift @@ -2906,249 +2906,3 @@ open class WebviewCookiesUpdateProtocolMock: WebviewCookiesUpdateProtocol, Mock } } -// MARK: - WebviewCookiesUpdateProtocol - -open class WebviewCookiesUpdateProtocolMock: WebviewCookiesUpdateProtocol, Mock { - public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { - SwiftyMockyTestObserver.setup() - self.sequencingPolicy = sequencingPolicy - self.stubbingPolicy = stubbingPolicy - self.file = file - self.line = line - } - - var matcher: Matcher = Matcher.default - var stubbingPolicy: StubbingPolicy = .wrap - var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst - - private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) - private var invocations: [MethodType] = [] - private var methodReturnValues: [Given] = [] - private var methodPerformValues: [Perform] = [] - private var file: StaticString? - private var line: UInt? - - public typealias PropertyStub = Given - public typealias MethodStub = Given - public typealias SubscriptStub = Given - - /// Convenience method - call setupMock() to extend debug information when failure occurs - public func setupMock(file: StaticString = #file, line: UInt = #line) { - self.file = file - self.line = line - } - - /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals - public func resetMock(_ scopes: MockScope...) { - let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes - if scopes.contains(.invocation) { invocations = [] } - if scopes.contains(.given) { methodReturnValues = [] } - if scopes.contains(.perform) { methodPerformValues = [] } - } - - public var authInteractor: AuthInteractorProtocol { - get { invocations.append(.p_authInteractor_get); return __p_authInteractor ?? givenGetterValue(.p_authInteractor_get, "WebviewCookiesUpdateProtocolMock - stub value for authInteractor was not defined") } - } - private var __p_authInteractor: (AuthInteractorProtocol)? - - public var cookiesReady: Bool { - get { invocations.append(.p_cookiesReady_get); return __p_cookiesReady ?? givenGetterValue(.p_cookiesReady_get, "WebviewCookiesUpdateProtocolMock - stub value for cookiesReady was not defined") } - set { invocations.append(.p_cookiesReady_set(.value(newValue))); __p_cookiesReady = newValue } - } - private var __p_cookiesReady: (Bool)? - - public var updatingCookies: Bool { - get { invocations.append(.p_updatingCookies_get); return __p_updatingCookies ?? givenGetterValue(.p_updatingCookies_get, "WebviewCookiesUpdateProtocolMock - stub value for updatingCookies was not defined") } - set { invocations.append(.p_updatingCookies_set(.value(newValue))); __p_updatingCookies = newValue } - } - private var __p_updatingCookies: (Bool)? - - public var errorMessage: String? { - get { invocations.append(.p_errorMessage_get); return __p_errorMessage ?? optionalGivenGetterValue(.p_errorMessage_get, "WebviewCookiesUpdateProtocolMock - stub value for errorMessage was not defined") } - set { invocations.append(.p_errorMessage_set(.value(newValue))); __p_errorMessage = newValue } - } - private var __p_errorMessage: (String)? - - - - - - open func updateCookies(force: Bool, retryCount: Int) { - addInvocation(.m_updateCookies__force_forceretryCount_retryCount(Parameter.value(`force`), Parameter.value(`retryCount`))) - let perform = methodPerformValue(.m_updateCookies__force_forceretryCount_retryCount(Parameter.value(`force`), Parameter.value(`retryCount`))) as? (Bool, Int) -> Void - perform?(`force`, `retryCount`) - } - - - fileprivate enum MethodType { - case m_updateCookies__force_forceretryCount_retryCount(Parameter, Parameter) - case p_authInteractor_get - case p_cookiesReady_get - case p_cookiesReady_set(Parameter) - case p_updatingCookies_get - case p_updatingCookies_set(Parameter) - case p_errorMessage_get - case p_errorMessage_set(Parameter) - - static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { - switch (lhs, rhs) { - case (.m_updateCookies__force_forceretryCount_retryCount(let lhsForce, let lhsRetrycount), .m_updateCookies__force_forceretryCount_retryCount(let rhsForce, let rhsRetrycount)): - var results: [Matcher.ParameterComparisonResult] = [] - results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsForce, rhs: rhsForce, with: matcher), lhsForce, rhsForce, "force")) - results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsRetrycount, rhs: rhsRetrycount, with: matcher), lhsRetrycount, rhsRetrycount, "retryCount")) - return Matcher.ComparisonResult(results) - case (.p_authInteractor_get,.p_authInteractor_get): return Matcher.ComparisonResult.match - case (.p_cookiesReady_get,.p_cookiesReady_get): return Matcher.ComparisonResult.match - case (.p_cookiesReady_set(let left),.p_cookiesReady_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) - case (.p_updatingCookies_get,.p_updatingCookies_get): return Matcher.ComparisonResult.match - case (.p_updatingCookies_set(let left),.p_updatingCookies_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) - case (.p_errorMessage_get,.p_errorMessage_get): return Matcher.ComparisonResult.match - case (.p_errorMessage_set(let left),.p_errorMessage_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) - default: return .none - } - } - - func intValue() -> Int { - switch self { - case let .m_updateCookies__force_forceretryCount_retryCount(p0, p1): return p0.intValue + p1.intValue - case .p_authInteractor_get: return 0 - case .p_cookiesReady_get: return 0 - case .p_cookiesReady_set(let newValue): return newValue.intValue - case .p_updatingCookies_get: return 0 - case .p_updatingCookies_set(let newValue): return newValue.intValue - case .p_errorMessage_get: return 0 - case .p_errorMessage_set(let newValue): return newValue.intValue - } - } - func assertionName() -> String { - switch self { - case .m_updateCookies__force_forceretryCount_retryCount: return ".updateCookies(force:retryCount:)" - case .p_authInteractor_get: return "[get] .authInteractor" - case .p_cookiesReady_get: return "[get] .cookiesReady" - case .p_cookiesReady_set: return "[set] .cookiesReady" - case .p_updatingCookies_get: return "[get] .updatingCookies" - case .p_updatingCookies_set: return "[set] .updatingCookies" - case .p_errorMessage_get: return "[get] .errorMessage" - case .p_errorMessage_set: return "[set] .errorMessage" - } - } - } - - open class Given: StubbedMethod { - fileprivate var method: MethodType - - private init(method: MethodType, products: [StubProduct]) { - self.method = method - super.init(products) - } - - public static func authInteractor(getter defaultValue: AuthInteractorProtocol...) -> PropertyStub { - return Given(method: .p_authInteractor_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) - } - public static func cookiesReady(getter defaultValue: Bool...) -> PropertyStub { - return Given(method: .p_cookiesReady_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) - } - public static func updatingCookies(getter defaultValue: Bool...) -> PropertyStub { - return Given(method: .p_updatingCookies_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) - } - public static func errorMessage(getter defaultValue: String?...) -> PropertyStub { - return Given(method: .p_errorMessage_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) - } - - } - - public struct Verify { - fileprivate var method: MethodType - - public static func updateCookies(force: Parameter, retryCount: Parameter) -> Verify { return Verify(method: .m_updateCookies__force_forceretryCount_retryCount(`force`, `retryCount`))} - public static var authInteractor: Verify { return Verify(method: .p_authInteractor_get) } - public static var cookiesReady: Verify { return Verify(method: .p_cookiesReady_get) } - public static func cookiesReady(set newValue: Parameter) -> Verify { return Verify(method: .p_cookiesReady_set(newValue)) } - public static var updatingCookies: Verify { return Verify(method: .p_updatingCookies_get) } - public static func updatingCookies(set newValue: Parameter) -> Verify { return Verify(method: .p_updatingCookies_set(newValue)) } - public static var errorMessage: Verify { return Verify(method: .p_errorMessage_get) } - public static func errorMessage(set newValue: Parameter) -> Verify { return Verify(method: .p_errorMessage_set(newValue)) } - } - - public struct Perform { - fileprivate var method: MethodType - var performs: Any - - public static func updateCookies(force: Parameter, retryCount: Parameter, perform: @escaping (Bool, Int) -> Void) -> Perform { - return Perform(method: .m_updateCookies__force_forceretryCount_retryCount(`force`, `retryCount`), performs: perform) - } - } - - public func given(_ method: Given) { - methodReturnValues.append(method) - } - - public func perform(_ method: Perform) { - methodPerformValues.append(method) - methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } - } - - public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { - let fullMatches = matchingCalls(method, file: file, line: line) - let success = count.matches(fullMatches) - let assertionName = method.method.assertionName() - let feedback: String = { - guard !success else { return "" } - return Utils.closestCallsMessage( - for: self.invocations.map { invocation in - matcher.set(file: file, line: line) - defer { matcher.clearFileAndLine() } - return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) - }, - name: assertionName - ) - }() - MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) - } - - private func addInvocation(_ call: MethodType) { - self.queue.sync { invocations.append(call) } - } - private func methodReturnValue(_ method: MethodType) throws -> StubProduct { - matcher.set(file: self.file, line: self.line) - defer { matcher.clearFileAndLine() } - let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) - let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) - guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } - return product - } - private func methodPerformValue(_ method: MethodType) -> Any? { - matcher.set(file: self.file, line: self.line) - defer { matcher.clearFileAndLine() } - let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } - return matched?.performs - } - private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { - matcher.set(file: file ?? self.file, line: line ?? self.line) - defer { matcher.clearFileAndLine() } - return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } - } - private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { - return matchingCalls(method.method, file: file, line: line).count - } - private func givenGetterValue(_ method: MethodType, _ message: String) -> T { - do { - return try methodReturnValue(method).casted() - } catch { - onFatalFailure(message) - Failure(message) - } - } - private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { - do { - return try methodReturnValue(method).casted() - } catch { - return nil - } - } - private func onFatalFailure(_ message: String) { - guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully - SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) - } -} - diff --git a/Dashboard/DashboardTests/DashboardMock.generated.swift b/Dashboard/DashboardTests/DashboardMock.generated.swift index a4d3d6380..b8a417000 100644 --- a/Dashboard/DashboardTests/DashboardMock.generated.swift +++ b/Dashboard/DashboardTests/DashboardMock.generated.swift @@ -2384,249 +2384,3 @@ open class WebviewCookiesUpdateProtocolMock: WebviewCookiesUpdateProtocol, Mock } } -// MARK: - WebviewCookiesUpdateProtocol - -open class WebviewCookiesUpdateProtocolMock: WebviewCookiesUpdateProtocol, Mock { - public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { - SwiftyMockyTestObserver.setup() - self.sequencingPolicy = sequencingPolicy - self.stubbingPolicy = stubbingPolicy - self.file = file - self.line = line - } - - var matcher: Matcher = Matcher.default - var stubbingPolicy: StubbingPolicy = .wrap - var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst - - private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) - private var invocations: [MethodType] = [] - private var methodReturnValues: [Given] = [] - private var methodPerformValues: [Perform] = [] - private var file: StaticString? - private var line: UInt? - - public typealias PropertyStub = Given - public typealias MethodStub = Given - public typealias SubscriptStub = Given - - /// Convenience method - call setupMock() to extend debug information when failure occurs - public func setupMock(file: StaticString = #file, line: UInt = #line) { - self.file = file - self.line = line - } - - /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals - public func resetMock(_ scopes: MockScope...) { - let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes - if scopes.contains(.invocation) { invocations = [] } - if scopes.contains(.given) { methodReturnValues = [] } - if scopes.contains(.perform) { methodPerformValues = [] } - } - - public var authInteractor: AuthInteractorProtocol { - get { invocations.append(.p_authInteractor_get); return __p_authInteractor ?? givenGetterValue(.p_authInteractor_get, "WebviewCookiesUpdateProtocolMock - stub value for authInteractor was not defined") } - } - private var __p_authInteractor: (AuthInteractorProtocol)? - - public var cookiesReady: Bool { - get { invocations.append(.p_cookiesReady_get); return __p_cookiesReady ?? givenGetterValue(.p_cookiesReady_get, "WebviewCookiesUpdateProtocolMock - stub value for cookiesReady was not defined") } - set { invocations.append(.p_cookiesReady_set(.value(newValue))); __p_cookiesReady = newValue } - } - private var __p_cookiesReady: (Bool)? - - public var updatingCookies: Bool { - get { invocations.append(.p_updatingCookies_get); return __p_updatingCookies ?? givenGetterValue(.p_updatingCookies_get, "WebviewCookiesUpdateProtocolMock - stub value for updatingCookies was not defined") } - set { invocations.append(.p_updatingCookies_set(.value(newValue))); __p_updatingCookies = newValue } - } - private var __p_updatingCookies: (Bool)? - - public var errorMessage: String? { - get { invocations.append(.p_errorMessage_get); return __p_errorMessage ?? optionalGivenGetterValue(.p_errorMessage_get, "WebviewCookiesUpdateProtocolMock - stub value for errorMessage was not defined") } - set { invocations.append(.p_errorMessage_set(.value(newValue))); __p_errorMessage = newValue } - } - private var __p_errorMessage: (String)? - - - - - - open func updateCookies(force: Bool, retryCount: Int) { - addInvocation(.m_updateCookies__force_forceretryCount_retryCount(Parameter.value(`force`), Parameter.value(`retryCount`))) - let perform = methodPerformValue(.m_updateCookies__force_forceretryCount_retryCount(Parameter.value(`force`), Parameter.value(`retryCount`))) as? (Bool, Int) -> Void - perform?(`force`, `retryCount`) - } - - - fileprivate enum MethodType { - case m_updateCookies__force_forceretryCount_retryCount(Parameter, Parameter) - case p_authInteractor_get - case p_cookiesReady_get - case p_cookiesReady_set(Parameter) - case p_updatingCookies_get - case p_updatingCookies_set(Parameter) - case p_errorMessage_get - case p_errorMessage_set(Parameter) - - static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { - switch (lhs, rhs) { - case (.m_updateCookies__force_forceretryCount_retryCount(let lhsForce, let lhsRetrycount), .m_updateCookies__force_forceretryCount_retryCount(let rhsForce, let rhsRetrycount)): - var results: [Matcher.ParameterComparisonResult] = [] - results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsForce, rhs: rhsForce, with: matcher), lhsForce, rhsForce, "force")) - results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsRetrycount, rhs: rhsRetrycount, with: matcher), lhsRetrycount, rhsRetrycount, "retryCount")) - return Matcher.ComparisonResult(results) - case (.p_authInteractor_get,.p_authInteractor_get): return Matcher.ComparisonResult.match - case (.p_cookiesReady_get,.p_cookiesReady_get): return Matcher.ComparisonResult.match - case (.p_cookiesReady_set(let left),.p_cookiesReady_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) - case (.p_updatingCookies_get,.p_updatingCookies_get): return Matcher.ComparisonResult.match - case (.p_updatingCookies_set(let left),.p_updatingCookies_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) - case (.p_errorMessage_get,.p_errorMessage_get): return Matcher.ComparisonResult.match - case (.p_errorMessage_set(let left),.p_errorMessage_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) - default: return .none - } - } - - func intValue() -> Int { - switch self { - case let .m_updateCookies__force_forceretryCount_retryCount(p0, p1): return p0.intValue + p1.intValue - case .p_authInteractor_get: return 0 - case .p_cookiesReady_get: return 0 - case .p_cookiesReady_set(let newValue): return newValue.intValue - case .p_updatingCookies_get: return 0 - case .p_updatingCookies_set(let newValue): return newValue.intValue - case .p_errorMessage_get: return 0 - case .p_errorMessage_set(let newValue): return newValue.intValue - } - } - func assertionName() -> String { - switch self { - case .m_updateCookies__force_forceretryCount_retryCount: return ".updateCookies(force:retryCount:)" - case .p_authInteractor_get: return "[get] .authInteractor" - case .p_cookiesReady_get: return "[get] .cookiesReady" - case .p_cookiesReady_set: return "[set] .cookiesReady" - case .p_updatingCookies_get: return "[get] .updatingCookies" - case .p_updatingCookies_set: return "[set] .updatingCookies" - case .p_errorMessage_get: return "[get] .errorMessage" - case .p_errorMessage_set: return "[set] .errorMessage" - } - } - } - - open class Given: StubbedMethod { - fileprivate var method: MethodType - - private init(method: MethodType, products: [StubProduct]) { - self.method = method - super.init(products) - } - - public static func authInteractor(getter defaultValue: AuthInteractorProtocol...) -> PropertyStub { - return Given(method: .p_authInteractor_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) - } - public static func cookiesReady(getter defaultValue: Bool...) -> PropertyStub { - return Given(method: .p_cookiesReady_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) - } - public static func updatingCookies(getter defaultValue: Bool...) -> PropertyStub { - return Given(method: .p_updatingCookies_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) - } - public static func errorMessage(getter defaultValue: String?...) -> PropertyStub { - return Given(method: .p_errorMessage_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) - } - - } - - public struct Verify { - fileprivate var method: MethodType - - public static func updateCookies(force: Parameter, retryCount: Parameter) -> Verify { return Verify(method: .m_updateCookies__force_forceretryCount_retryCount(`force`, `retryCount`))} - public static var authInteractor: Verify { return Verify(method: .p_authInteractor_get) } - public static var cookiesReady: Verify { return Verify(method: .p_cookiesReady_get) } - public static func cookiesReady(set newValue: Parameter) -> Verify { return Verify(method: .p_cookiesReady_set(newValue)) } - public static var updatingCookies: Verify { return Verify(method: .p_updatingCookies_get) } - public static func updatingCookies(set newValue: Parameter) -> Verify { return Verify(method: .p_updatingCookies_set(newValue)) } - public static var errorMessage: Verify { return Verify(method: .p_errorMessage_get) } - public static func errorMessage(set newValue: Parameter) -> Verify { return Verify(method: .p_errorMessage_set(newValue)) } - } - - public struct Perform { - fileprivate var method: MethodType - var performs: Any - - public static func updateCookies(force: Parameter, retryCount: Parameter, perform: @escaping (Bool, Int) -> Void) -> Perform { - return Perform(method: .m_updateCookies__force_forceretryCount_retryCount(`force`, `retryCount`), performs: perform) - } - } - - public func given(_ method: Given) { - methodReturnValues.append(method) - } - - public func perform(_ method: Perform) { - methodPerformValues.append(method) - methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } - } - - public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { - let fullMatches = matchingCalls(method, file: file, line: line) - let success = count.matches(fullMatches) - let assertionName = method.method.assertionName() - let feedback: String = { - guard !success else { return "" } - return Utils.closestCallsMessage( - for: self.invocations.map { invocation in - matcher.set(file: file, line: line) - defer { matcher.clearFileAndLine() } - return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) - }, - name: assertionName - ) - }() - MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) - } - - private func addInvocation(_ call: MethodType) { - self.queue.sync { invocations.append(call) } - } - private func methodReturnValue(_ method: MethodType) throws -> StubProduct { - matcher.set(file: self.file, line: self.line) - defer { matcher.clearFileAndLine() } - let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) - let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) - guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } - return product - } - private func methodPerformValue(_ method: MethodType) -> Any? { - matcher.set(file: self.file, line: self.line) - defer { matcher.clearFileAndLine() } - let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } - return matched?.performs - } - private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { - matcher.set(file: file ?? self.file, line: line ?? self.line) - defer { matcher.clearFileAndLine() } - return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } - } - private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { - return matchingCalls(method.method, file: file, line: line).count - } - private func givenGetterValue(_ method: MethodType, _ message: String) -> T { - do { - return try methodReturnValue(method).casted() - } catch { - onFatalFailure(message) - Failure(message) - } - } - private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { - do { - return try methodReturnValue(method).casted() - } catch { - return nil - } - } - private func onFatalFailure(_ message: String) { - guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully - SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) - } -} - diff --git a/Discovery/DiscoveryTests/DiscoveryMock.generated.swift b/Discovery/DiscoveryTests/DiscoveryMock.generated.swift index 8014ac0df..ef22e064f 100644 --- a/Discovery/DiscoveryTests/DiscoveryMock.generated.swift +++ b/Discovery/DiscoveryTests/DiscoveryMock.generated.swift @@ -2641,249 +2641,3 @@ open class WebviewCookiesUpdateProtocolMock: WebviewCookiesUpdateProtocol, Mock } } -// MARK: - WebviewCookiesUpdateProtocol - -open class WebviewCookiesUpdateProtocolMock: WebviewCookiesUpdateProtocol, Mock { - public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { - SwiftyMockyTestObserver.setup() - self.sequencingPolicy = sequencingPolicy - self.stubbingPolicy = stubbingPolicy - self.file = file - self.line = line - } - - var matcher: Matcher = Matcher.default - var stubbingPolicy: StubbingPolicy = .wrap - var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst - - private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) - private var invocations: [MethodType] = [] - private var methodReturnValues: [Given] = [] - private var methodPerformValues: [Perform] = [] - private var file: StaticString? - private var line: UInt? - - public typealias PropertyStub = Given - public typealias MethodStub = Given - public typealias SubscriptStub = Given - - /// Convenience method - call setupMock() to extend debug information when failure occurs - public func setupMock(file: StaticString = #file, line: UInt = #line) { - self.file = file - self.line = line - } - - /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals - public func resetMock(_ scopes: MockScope...) { - let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes - if scopes.contains(.invocation) { invocations = [] } - if scopes.contains(.given) { methodReturnValues = [] } - if scopes.contains(.perform) { methodPerformValues = [] } - } - - public var authInteractor: AuthInteractorProtocol { - get { invocations.append(.p_authInteractor_get); return __p_authInteractor ?? givenGetterValue(.p_authInteractor_get, "WebviewCookiesUpdateProtocolMock - stub value for authInteractor was not defined") } - } - private var __p_authInteractor: (AuthInteractorProtocol)? - - public var cookiesReady: Bool { - get { invocations.append(.p_cookiesReady_get); return __p_cookiesReady ?? givenGetterValue(.p_cookiesReady_get, "WebviewCookiesUpdateProtocolMock - stub value for cookiesReady was not defined") } - set { invocations.append(.p_cookiesReady_set(.value(newValue))); __p_cookiesReady = newValue } - } - private var __p_cookiesReady: (Bool)? - - public var updatingCookies: Bool { - get { invocations.append(.p_updatingCookies_get); return __p_updatingCookies ?? givenGetterValue(.p_updatingCookies_get, "WebviewCookiesUpdateProtocolMock - stub value for updatingCookies was not defined") } - set { invocations.append(.p_updatingCookies_set(.value(newValue))); __p_updatingCookies = newValue } - } - private var __p_updatingCookies: (Bool)? - - public var errorMessage: String? { - get { invocations.append(.p_errorMessage_get); return __p_errorMessage ?? optionalGivenGetterValue(.p_errorMessage_get, "WebviewCookiesUpdateProtocolMock - stub value for errorMessage was not defined") } - set { invocations.append(.p_errorMessage_set(.value(newValue))); __p_errorMessage = newValue } - } - private var __p_errorMessage: (String)? - - - - - - open func updateCookies(force: Bool, retryCount: Int) { - addInvocation(.m_updateCookies__force_forceretryCount_retryCount(Parameter.value(`force`), Parameter.value(`retryCount`))) - let perform = methodPerformValue(.m_updateCookies__force_forceretryCount_retryCount(Parameter.value(`force`), Parameter.value(`retryCount`))) as? (Bool, Int) -> Void - perform?(`force`, `retryCount`) - } - - - fileprivate enum MethodType { - case m_updateCookies__force_forceretryCount_retryCount(Parameter, Parameter) - case p_authInteractor_get - case p_cookiesReady_get - case p_cookiesReady_set(Parameter) - case p_updatingCookies_get - case p_updatingCookies_set(Parameter) - case p_errorMessage_get - case p_errorMessage_set(Parameter) - - static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { - switch (lhs, rhs) { - case (.m_updateCookies__force_forceretryCount_retryCount(let lhsForce, let lhsRetrycount), .m_updateCookies__force_forceretryCount_retryCount(let rhsForce, let rhsRetrycount)): - var results: [Matcher.ParameterComparisonResult] = [] - results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsForce, rhs: rhsForce, with: matcher), lhsForce, rhsForce, "force")) - results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsRetrycount, rhs: rhsRetrycount, with: matcher), lhsRetrycount, rhsRetrycount, "retryCount")) - return Matcher.ComparisonResult(results) - case (.p_authInteractor_get,.p_authInteractor_get): return Matcher.ComparisonResult.match - case (.p_cookiesReady_get,.p_cookiesReady_get): return Matcher.ComparisonResult.match - case (.p_cookiesReady_set(let left),.p_cookiesReady_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) - case (.p_updatingCookies_get,.p_updatingCookies_get): return Matcher.ComparisonResult.match - case (.p_updatingCookies_set(let left),.p_updatingCookies_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) - case (.p_errorMessage_get,.p_errorMessage_get): return Matcher.ComparisonResult.match - case (.p_errorMessage_set(let left),.p_errorMessage_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) - default: return .none - } - } - - func intValue() -> Int { - switch self { - case let .m_updateCookies__force_forceretryCount_retryCount(p0, p1): return p0.intValue + p1.intValue - case .p_authInteractor_get: return 0 - case .p_cookiesReady_get: return 0 - case .p_cookiesReady_set(let newValue): return newValue.intValue - case .p_updatingCookies_get: return 0 - case .p_updatingCookies_set(let newValue): return newValue.intValue - case .p_errorMessage_get: return 0 - case .p_errorMessage_set(let newValue): return newValue.intValue - } - } - func assertionName() -> String { - switch self { - case .m_updateCookies__force_forceretryCount_retryCount: return ".updateCookies(force:retryCount:)" - case .p_authInteractor_get: return "[get] .authInteractor" - case .p_cookiesReady_get: return "[get] .cookiesReady" - case .p_cookiesReady_set: return "[set] .cookiesReady" - case .p_updatingCookies_get: return "[get] .updatingCookies" - case .p_updatingCookies_set: return "[set] .updatingCookies" - case .p_errorMessage_get: return "[get] .errorMessage" - case .p_errorMessage_set: return "[set] .errorMessage" - } - } - } - - open class Given: StubbedMethod { - fileprivate var method: MethodType - - private init(method: MethodType, products: [StubProduct]) { - self.method = method - super.init(products) - } - - public static func authInteractor(getter defaultValue: AuthInteractorProtocol...) -> PropertyStub { - return Given(method: .p_authInteractor_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) - } - public static func cookiesReady(getter defaultValue: Bool...) -> PropertyStub { - return Given(method: .p_cookiesReady_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) - } - public static func updatingCookies(getter defaultValue: Bool...) -> PropertyStub { - return Given(method: .p_updatingCookies_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) - } - public static func errorMessage(getter defaultValue: String?...) -> PropertyStub { - return Given(method: .p_errorMessage_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) - } - - } - - public struct Verify { - fileprivate var method: MethodType - - public static func updateCookies(force: Parameter, retryCount: Parameter) -> Verify { return Verify(method: .m_updateCookies__force_forceretryCount_retryCount(`force`, `retryCount`))} - public static var authInteractor: Verify { return Verify(method: .p_authInteractor_get) } - public static var cookiesReady: Verify { return Verify(method: .p_cookiesReady_get) } - public static func cookiesReady(set newValue: Parameter) -> Verify { return Verify(method: .p_cookiesReady_set(newValue)) } - public static var updatingCookies: Verify { return Verify(method: .p_updatingCookies_get) } - public static func updatingCookies(set newValue: Parameter) -> Verify { return Verify(method: .p_updatingCookies_set(newValue)) } - public static var errorMessage: Verify { return Verify(method: .p_errorMessage_get) } - public static func errorMessage(set newValue: Parameter) -> Verify { return Verify(method: .p_errorMessage_set(newValue)) } - } - - public struct Perform { - fileprivate var method: MethodType - var performs: Any - - public static func updateCookies(force: Parameter, retryCount: Parameter, perform: @escaping (Bool, Int) -> Void) -> Perform { - return Perform(method: .m_updateCookies__force_forceretryCount_retryCount(`force`, `retryCount`), performs: perform) - } - } - - public func given(_ method: Given) { - methodReturnValues.append(method) - } - - public func perform(_ method: Perform) { - methodPerformValues.append(method) - methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } - } - - public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { - let fullMatches = matchingCalls(method, file: file, line: line) - let success = count.matches(fullMatches) - let assertionName = method.method.assertionName() - let feedback: String = { - guard !success else { return "" } - return Utils.closestCallsMessage( - for: self.invocations.map { invocation in - matcher.set(file: file, line: line) - defer { matcher.clearFileAndLine() } - return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) - }, - name: assertionName - ) - }() - MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) - } - - private func addInvocation(_ call: MethodType) { - self.queue.sync { invocations.append(call) } - } - private func methodReturnValue(_ method: MethodType) throws -> StubProduct { - matcher.set(file: self.file, line: self.line) - defer { matcher.clearFileAndLine() } - let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) - let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) - guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } - return product - } - private func methodPerformValue(_ method: MethodType) -> Any? { - matcher.set(file: self.file, line: self.line) - defer { matcher.clearFileAndLine() } - let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } - return matched?.performs - } - private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { - matcher.set(file: file ?? self.file, line: line ?? self.line) - defer { matcher.clearFileAndLine() } - return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } - } - private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { - return matchingCalls(method.method, file: file, line: line).count - } - private func givenGetterValue(_ method: MethodType, _ message: String) -> T { - do { - return try methodReturnValue(method).casted() - } catch { - onFatalFailure(message) - Failure(message) - } - } - private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { - do { - return try methodReturnValue(method).casted() - } catch { - return nil - } - } - private func onFatalFailure(_ message: String) { - guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully - SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) - } -} - diff --git a/Discussion/DiscussionTests/DiscussionMock.generated.swift b/Discussion/DiscussionTests/DiscussionMock.generated.swift index be052b0d4..336d50a14 100644 --- a/Discussion/DiscussionTests/DiscussionMock.generated.swift +++ b/Discussion/DiscussionTests/DiscussionMock.generated.swift @@ -3462,249 +3462,3 @@ open class WebviewCookiesUpdateProtocolMock: WebviewCookiesUpdateProtocol, Mock } } -// MARK: - WebviewCookiesUpdateProtocol - -open class WebviewCookiesUpdateProtocolMock: WebviewCookiesUpdateProtocol, Mock { - public init(sequencing sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst, stubbing stubbingPolicy: StubbingPolicy = .wrap, file: StaticString = #file, line: UInt = #line) { - SwiftyMockyTestObserver.setup() - self.sequencingPolicy = sequencingPolicy - self.stubbingPolicy = stubbingPolicy - self.file = file - self.line = line - } - - var matcher: Matcher = Matcher.default - var stubbingPolicy: StubbingPolicy = .wrap - var sequencingPolicy: SequencingPolicy = .lastWrittenResolvedFirst - - private var queue = DispatchQueue(label: "com.swiftymocky.invocations", qos: .userInteractive) - private var invocations: [MethodType] = [] - private var methodReturnValues: [Given] = [] - private var methodPerformValues: [Perform] = [] - private var file: StaticString? - private var line: UInt? - - public typealias PropertyStub = Given - public typealias MethodStub = Given - public typealias SubscriptStub = Given - - /// Convenience method - call setupMock() to extend debug information when failure occurs - public func setupMock(file: StaticString = #file, line: UInt = #line) { - self.file = file - self.line = line - } - - /// Clear mock internals. You can specify what to reset (invocations aka verify, givens or performs) or leave it empty to clear all mock internals - public func resetMock(_ scopes: MockScope...) { - let scopes: [MockScope] = scopes.isEmpty ? [.invocation, .given, .perform] : scopes - if scopes.contains(.invocation) { invocations = [] } - if scopes.contains(.given) { methodReturnValues = [] } - if scopes.contains(.perform) { methodPerformValues = [] } - } - - public var authInteractor: AuthInteractorProtocol { - get { invocations.append(.p_authInteractor_get); return __p_authInteractor ?? givenGetterValue(.p_authInteractor_get, "WebviewCookiesUpdateProtocolMock - stub value for authInteractor was not defined") } - } - private var __p_authInteractor: (AuthInteractorProtocol)? - - public var cookiesReady: Bool { - get { invocations.append(.p_cookiesReady_get); return __p_cookiesReady ?? givenGetterValue(.p_cookiesReady_get, "WebviewCookiesUpdateProtocolMock - stub value for cookiesReady was not defined") } - set { invocations.append(.p_cookiesReady_set(.value(newValue))); __p_cookiesReady = newValue } - } - private var __p_cookiesReady: (Bool)? - - public var updatingCookies: Bool { - get { invocations.append(.p_updatingCookies_get); return __p_updatingCookies ?? givenGetterValue(.p_updatingCookies_get, "WebviewCookiesUpdateProtocolMock - stub value for updatingCookies was not defined") } - set { invocations.append(.p_updatingCookies_set(.value(newValue))); __p_updatingCookies = newValue } - } - private var __p_updatingCookies: (Bool)? - - public var errorMessage: String? { - get { invocations.append(.p_errorMessage_get); return __p_errorMessage ?? optionalGivenGetterValue(.p_errorMessage_get, "WebviewCookiesUpdateProtocolMock - stub value for errorMessage was not defined") } - set { invocations.append(.p_errorMessage_set(.value(newValue))); __p_errorMessage = newValue } - } - private var __p_errorMessage: (String)? - - - - - - open func updateCookies(force: Bool, retryCount: Int) { - addInvocation(.m_updateCookies__force_forceretryCount_retryCount(Parameter.value(`force`), Parameter.value(`retryCount`))) - let perform = methodPerformValue(.m_updateCookies__force_forceretryCount_retryCount(Parameter.value(`force`), Parameter.value(`retryCount`))) as? (Bool, Int) -> Void - perform?(`force`, `retryCount`) - } - - - fileprivate enum MethodType { - case m_updateCookies__force_forceretryCount_retryCount(Parameter, Parameter) - case p_authInteractor_get - case p_cookiesReady_get - case p_cookiesReady_set(Parameter) - case p_updatingCookies_get - case p_updatingCookies_set(Parameter) - case p_errorMessage_get - case p_errorMessage_set(Parameter) - - static func compareParameters(lhs: MethodType, rhs: MethodType, matcher: Matcher) -> Matcher.ComparisonResult { - switch (lhs, rhs) { - case (.m_updateCookies__force_forceretryCount_retryCount(let lhsForce, let lhsRetrycount), .m_updateCookies__force_forceretryCount_retryCount(let rhsForce, let rhsRetrycount)): - var results: [Matcher.ParameterComparisonResult] = [] - results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsForce, rhs: rhsForce, with: matcher), lhsForce, rhsForce, "force")) - results.append(Matcher.ParameterComparisonResult(Parameter.compare(lhs: lhsRetrycount, rhs: rhsRetrycount, with: matcher), lhsRetrycount, rhsRetrycount, "retryCount")) - return Matcher.ComparisonResult(results) - case (.p_authInteractor_get,.p_authInteractor_get): return Matcher.ComparisonResult.match - case (.p_cookiesReady_get,.p_cookiesReady_get): return Matcher.ComparisonResult.match - case (.p_cookiesReady_set(let left),.p_cookiesReady_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) - case (.p_updatingCookies_get,.p_updatingCookies_get): return Matcher.ComparisonResult.match - case (.p_updatingCookies_set(let left),.p_updatingCookies_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) - case (.p_errorMessage_get,.p_errorMessage_get): return Matcher.ComparisonResult.match - case (.p_errorMessage_set(let left),.p_errorMessage_set(let right)): return Matcher.ComparisonResult([Matcher.ParameterComparisonResult(Parameter.compare(lhs: left, rhs: right, with: matcher), left, right, "newValue")]) - default: return .none - } - } - - func intValue() -> Int { - switch self { - case let .m_updateCookies__force_forceretryCount_retryCount(p0, p1): return p0.intValue + p1.intValue - case .p_authInteractor_get: return 0 - case .p_cookiesReady_get: return 0 - case .p_cookiesReady_set(let newValue): return newValue.intValue - case .p_updatingCookies_get: return 0 - case .p_updatingCookies_set(let newValue): return newValue.intValue - case .p_errorMessage_get: return 0 - case .p_errorMessage_set(let newValue): return newValue.intValue - } - } - func assertionName() -> String { - switch self { - case .m_updateCookies__force_forceretryCount_retryCount: return ".updateCookies(force:retryCount:)" - case .p_authInteractor_get: return "[get] .authInteractor" - case .p_cookiesReady_get: return "[get] .cookiesReady" - case .p_cookiesReady_set: return "[set] .cookiesReady" - case .p_updatingCookies_get: return "[get] .updatingCookies" - case .p_updatingCookies_set: return "[set] .updatingCookies" - case .p_errorMessage_get: return "[get] .errorMessage" - case .p_errorMessage_set: return "[set] .errorMessage" - } - } - } - - open class Given: StubbedMethod { - fileprivate var method: MethodType - - private init(method: MethodType, products: [StubProduct]) { - self.method = method - super.init(products) - } - - public static func authInteractor(getter defaultValue: AuthInteractorProtocol...) -> PropertyStub { - return Given(method: .p_authInteractor_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) - } - public static func cookiesReady(getter defaultValue: Bool...) -> PropertyStub { - return Given(method: .p_cookiesReady_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) - } - public static func updatingCookies(getter defaultValue: Bool...) -> PropertyStub { - return Given(method: .p_updatingCookies_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) - } - public static func errorMessage(getter defaultValue: String?...) -> PropertyStub { - return Given(method: .p_errorMessage_get, products: defaultValue.map({ StubProduct.return($0 as Any) })) - } - - } - - public struct Verify { - fileprivate var method: MethodType - - public static func updateCookies(force: Parameter, retryCount: Parameter) -> Verify { return Verify(method: .m_updateCookies__force_forceretryCount_retryCount(`force`, `retryCount`))} - public static var authInteractor: Verify { return Verify(method: .p_authInteractor_get) } - public static var cookiesReady: Verify { return Verify(method: .p_cookiesReady_get) } - public static func cookiesReady(set newValue: Parameter) -> Verify { return Verify(method: .p_cookiesReady_set(newValue)) } - public static var updatingCookies: Verify { return Verify(method: .p_updatingCookies_get) } - public static func updatingCookies(set newValue: Parameter) -> Verify { return Verify(method: .p_updatingCookies_set(newValue)) } - public static var errorMessage: Verify { return Verify(method: .p_errorMessage_get) } - public static func errorMessage(set newValue: Parameter) -> Verify { return Verify(method: .p_errorMessage_set(newValue)) } - } - - public struct Perform { - fileprivate var method: MethodType - var performs: Any - - public static func updateCookies(force: Parameter, retryCount: Parameter, perform: @escaping (Bool, Int) -> Void) -> Perform { - return Perform(method: .m_updateCookies__force_forceretryCount_retryCount(`force`, `retryCount`), performs: perform) - } - } - - public func given(_ method: Given) { - methodReturnValues.append(method) - } - - public func perform(_ method: Perform) { - methodPerformValues.append(method) - methodPerformValues.sort { $0.method.intValue() < $1.method.intValue() } - } - - public func verify(_ method: Verify, count: Count = Count.moreOrEqual(to: 1), file: StaticString = #file, line: UInt = #line) { - let fullMatches = matchingCalls(method, file: file, line: line) - let success = count.matches(fullMatches) - let assertionName = method.method.assertionName() - let feedback: String = { - guard !success else { return "" } - return Utils.closestCallsMessage( - for: self.invocations.map { invocation in - matcher.set(file: file, line: line) - defer { matcher.clearFileAndLine() } - return MethodType.compareParameters(lhs: invocation, rhs: method.method, matcher: matcher) - }, - name: assertionName - ) - }() - MockyAssert(success, "Expected: \(count) invocations of `\(assertionName)`, but was: \(fullMatches).\(feedback)", file: file, line: line) - } - - private func addInvocation(_ call: MethodType) { - self.queue.sync { invocations.append(call) } - } - private func methodReturnValue(_ method: MethodType) throws -> StubProduct { - matcher.set(file: self.file, line: self.line) - defer { matcher.clearFileAndLine() } - let candidates = sequencingPolicy.sorted(methodReturnValues, by: { $0.method.intValue() > $1.method.intValue() }) - let matched = candidates.first(where: { $0.isValid && MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch }) - guard let product = matched?.getProduct(policy: self.stubbingPolicy) else { throw MockError.notStubed } - return product - } - private func methodPerformValue(_ method: MethodType) -> Any? { - matcher.set(file: self.file, line: self.line) - defer { matcher.clearFileAndLine() } - let matched = methodPerformValues.reversed().first { MethodType.compareParameters(lhs: $0.method, rhs: method, matcher: matcher).isFullMatch } - return matched?.performs - } - private func matchingCalls(_ method: MethodType, file: StaticString?, line: UInt?) -> [MethodType] { - matcher.set(file: file ?? self.file, line: line ?? self.line) - defer { matcher.clearFileAndLine() } - return invocations.filter { MethodType.compareParameters(lhs: $0, rhs: method, matcher: matcher).isFullMatch } - } - private func matchingCalls(_ method: Verify, file: StaticString?, line: UInt?) -> Int { - return matchingCalls(method.method, file: file, line: line).count - } - private func givenGetterValue(_ method: MethodType, _ message: String) -> T { - do { - return try methodReturnValue(method).casted() - } catch { - onFatalFailure(message) - Failure(message) - } - } - private func optionalGivenGetterValue(_ method: MethodType, _ message: String) -> T? { - do { - return try methodReturnValue(method).casted() - } catch { - return nil - } - } - private func onFatalFailure(_ message: String) { - guard let file = self.file, let line = self.line else { return } // Let if fail if cannot handle gratefully - SwiftyMockyTestObserver.handleFatalError(message: message, file: file, line: line) - } -} -