diff --git a/CHANGELOG.md b/CHANGELOG.md index 79a827402..91f16e88d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,25 @@ The changelog for `SuperwallKit`. Also see the [releases](https://github.com/superwall-me/SuperwallKit-iOS/releases) on GitHub. -## 3.0.0 (Upcoming Release) +## 3.0.0-beta.2 + +### Breaking Changes + +- Moves all functions and variables to the `shared` instance for consistency, e.g. it's now `Superwall.shared.track()` instead of `Superwall.track()`. + +### Enhancements + +- Readds `Superwall.shared.logLevel` as a top level static convenience variable so you can easily change the log level. +- Adds `isLoggedIn` to user properties, which means you can create a rule based on whether the user is logged in vs. whether they're anonymous. + +### Fixes + +- Fixes bug in `(false) @@ -33,12 +33,12 @@ final class SuperwallService { // } // Getting our logged in status to Superwall. - shared.isLoggedIn.send(Superwall.isLoggedIn) + shared.isLoggedIn.send(Superwall.shared.isLoggedIn) } static func logIn() async { do { - try await Superwall.logIn(userId: "abc") + try await Superwall.shared.logIn(userId: "abc") } catch let error as IdentityError { switch error { case .missingUserId: @@ -53,7 +53,7 @@ final class SuperwallService { static func logOut() async { do { - try await Superwall.logOut() + try await Superwall.shared.logOut() } catch LogoutError.notLoggedIn { print("The user is not logged in") } catch { @@ -62,11 +62,11 @@ final class SuperwallService { } static func handleDeepLink(_ url: URL) { - Superwall.handleDeepLink(url) + Superwall.shared.handleDeepLink(url) } static func setName(to name: String) { - Superwall.setUserAttributes(["firstName": name]) + Superwall.shared.setUserAttributes(["firstName": name]) } } diff --git a/Examples/SwiftUI/Superwall-SwiftUI/TrackEventModel.swift b/Examples/SwiftUI/Superwall-SwiftUI/TrackEventModel.swift index 23b689300..a965c9ca0 100644 --- a/Examples/SwiftUI/Superwall-SwiftUI/TrackEventModel.swift +++ b/Examples/SwiftUI/Superwall-SwiftUI/TrackEventModel.swift @@ -12,7 +12,7 @@ final class TrackEventModel { // private var cancellable: AnyCancellable? func trackEvent() { - Superwall.track(event: "MyEvent") { paywallState in + Superwall.shared.track(event: "MyEvent") { paywallState in switch paywallState { case .presented(let paywallInfo): print("paywall info is", paywallInfo) diff --git a/Examples/UIKit+RevenueCat/README.md b/Examples/UIKit+RevenueCat/README.md index 13705974d..7310041f3 100644 --- a/Examples/UIKit+RevenueCat/README.md +++ b/Examples/UIKit+RevenueCat/README.md @@ -53,9 +53,9 @@ The SDK sends back events received from the paywall via the delegate methods in ## Logging In -On the welcome screen, enter your name in the **text field**. This saves to the Superwall user attributes using [Superwall.setUserAttributes(_:)](Superwall-UIKit+RevenueCat/PaywallManager.swift#L97). You don't need to set user attributes, but it can be useful if you want to create a rule to present a paywall based on a specific attribute you've set. You can also recall user attributes on your paywall to personalise the messaging. +On the welcome screen, enter your name in the **text field**. This saves to the Superwall user attributes using [Superwall.shared.setUserAttributes(_:)](Superwall-UIKit+RevenueCat/PaywallManager.swift#L97). You don't need to set user attributes, but it can be useful if you want to create a rule to present a paywall based on a specific attribute you've set. You can also recall user attributes on your paywall to personalise the messaging. -Tap **Log In**. This logs the user in to Superwall (with a hardcoded userId that we've set), retrieving any paywalls that have already been assigned to them. If you were to create a new account you'd use `Superwall.createAccount(userId:)` instead. +Tap **Log In**. This logs the user in to Superwall (with a hardcoded userId that we've set), retrieving any paywalls that have already been assigned to them. If you were to create a new account you'd use `Superwall.shared.createAccount(userId:)` instead. You'll see an overview screen: @@ -71,7 +71,7 @@ On the [Superwall Dashboard](https://superwall.com/dashboard) you add this event When an event is tracked, SuperwallKit evaluates the rules associated with it to determine whether or not to show a paywall. Note that if the delegate method [isUserSubscribed()](Superwall-UIKit+RevenueCat/PaywallManager.swift#L163) returns `true`, a paywall will not show by default. -By calling [Superwall.track(event:params:paywallOverrides:paywallHandler:)](Superwall-UIKit+RevenueCat/TrackEventViewController.swift#L59), you present a paywall in response to the event. For this app, the event is called "MyEvent". +By calling [Superwall.shared.track(event:params:paywallOverrides:paywallHandler:)](Superwall-UIKit+RevenueCat/TrackEventViewController.swift#L59), you present a paywall in response to the event. For this app, the event is called "MyEvent". On screen you'll see some explanatory text and a button that tracks an event: diff --git a/Examples/UIKit+RevenueCat/Superwall-UIKit+RevenueCat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/UIKit+RevenueCat/Superwall-UIKit+RevenueCat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 767d880fd..75e1345dd 100644 --- a/Examples/UIKit+RevenueCat/Superwall-UIKit+RevenueCat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/UIKit+RevenueCat/Superwall-UIKit+RevenueCat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/RevenueCat/purchases-ios.git", "state": { "branch": null, - "revision": "b1132d43125832409934f0fe5c8642ceb37d60b9", - "version": "4.14.3" + "revision": "390073e0dec72f2a4aabb1888ceda46a059808e5", + "version": "4.17.3" } } ] diff --git a/Examples/UIKit+RevenueCat/Superwall-UIKit+RevenueCat/PaywallManager.swift b/Examples/UIKit+RevenueCat/Superwall-UIKit+RevenueCat/PaywallManager.swift index 89edbbe40..5f8bf177d 100644 --- a/Examples/UIKit+RevenueCat/Superwall-UIKit+RevenueCat/PaywallManager.swift +++ b/Examples/UIKit+RevenueCat/Superwall-UIKit+RevenueCat/PaywallManager.swift @@ -13,7 +13,7 @@ import Combine final class PaywallManager: NSObject { static let shared = PaywallManager() static var name: String { - return Superwall.userAttributes["firstName"] as? String ?? "" + return Superwall.shared.userAttributes["firstName"] as? String ?? "" } @Published var isSubscribed = false { didSet { @@ -59,11 +59,11 @@ final class PaywallManager: NSObject { /// Logs the user in to both RevenueCat and Superwall with the specified `userId`. /// /// Call this when your user needs to log in. - static func logIn(userId: String) async { + func logIn(userId: String) async { do { let (customerInfo, _) = try await Purchases.shared.logIn(userId) - shared.updateSubscriptionStatus(using: customerInfo) - try await Superwall.logIn(userId: userId) + updateSubscriptionStatus(using: customerInfo) + try await Superwall.shared.logIn(userId: userId) } catch let error as IdentityError { switch error { case .alreadyLoggedIn: @@ -83,7 +83,7 @@ final class PaywallManager: NSObject { do { let customerInfo = try await Purchases.shared.logOut() updateSubscriptionStatus(using: customerInfo) - try await Superwall.logOut() + try await Superwall.shared.logOut() } catch let error as LogoutError { switch error { case .notLoggedIn: @@ -99,18 +99,18 @@ final class PaywallManager: NSObject { /// [See here](https://docs.superwall.com/v3.0/docs/in-app-paywall-previews#handling-deep-links) /// for information on how to call this function in your app. static func handleDeepLink(_ url: URL) { - Superwall.handleDeepLink(url) + Superwall.shared.handleDeepLink(url) } /// Settting Superwall attributes. static func setName(to name: String) { - Superwall.setUserAttributes(["firstName": name]) + Superwall.shared.setUserAttributes(["firstName": name]) } /// Purchases a product with RevenueCat. /// - Returns: A boolean indicating whether the user cancelled or not. private func purchase(_ product: SKProduct) async throws -> Bool { - let product = await Purchases.shared.products([product.productIdentifier]).first! + let product = RevenueCat.StoreProduct(sk1Product: product) let (_, customerInfo, userCancelled) = try await Purchases.shared.purchase(product: product) updateSubscriptionStatus(using: customerInfo) return userCancelled diff --git a/Examples/UIKit+RevenueCat/Superwall-UIKit+RevenueCat/TrackEventViewController.swift b/Examples/UIKit+RevenueCat/Superwall-UIKit+RevenueCat/TrackEventViewController.swift index f11a3744f..a30046f41 100644 --- a/Examples/UIKit+RevenueCat/Superwall-UIKit+RevenueCat/TrackEventViewController.swift +++ b/Examples/UIKit+RevenueCat/Superwall-UIKit+RevenueCat/TrackEventViewController.swift @@ -57,7 +57,7 @@ final class TrackEventViewController: UIViewController { } @IBAction private func trackEvent() { - Superwall.track(event: "MyEvent") { paywallState in + Superwall.shared.track(event: "MyEvent") { paywallState in switch paywallState { case .presented(let paywallInfo): print("paywall info is", paywallInfo) diff --git a/Examples/UIKit+RevenueCat/Superwall-UIKit+RevenueCat/WelcomeViewController.swift b/Examples/UIKit+RevenueCat/Superwall-UIKit+RevenueCat/WelcomeViewController.swift index db06e1328..ba688ad49 100644 --- a/Examples/UIKit+RevenueCat/Superwall-UIKit+RevenueCat/WelcomeViewController.swift +++ b/Examples/UIKit+RevenueCat/Superwall-UIKit+RevenueCat/WelcomeViewController.swift @@ -21,7 +21,7 @@ final class WelcomeViewController: UIViewController { super.viewDidLoad() isLoggedIn = UserDefaults.standard.bool(forKey: "IsLoggedIn") - + if isLoggedIn { next() } @@ -42,7 +42,7 @@ final class WelcomeViewController: UIViewController { PaywallManager.setName(to: name) } let userId = "abc" - await PaywallManager.logIn(userId: userId) + await PaywallManager.shared.logIn(userId: userId) next() } diff --git a/Examples/UIKit-ObjC/Superwall-UIKit-ObjC/SSATrackEventViewController.m b/Examples/UIKit-ObjC/Superwall-UIKit-ObjC/SSATrackEventViewController.m index 2c76299ed..fd996e0c8 100644 --- a/Examples/UIKit-ObjC/Superwall-UIKit-ObjC/SSATrackEventViewController.m +++ b/Examples/UIKit-ObjC/Superwall-UIKit-ObjC/SSATrackEventViewController.m @@ -47,7 +47,7 @@ - (void)viewWillAppear:(BOOL)animated { - (IBAction)trackEvent:(id)sender { __weak typeof(self) weakSelf = self; - [Superwall trackWithEvent:@"MyEvent" + [[Superwall sharedInstance] trackWithEvent:@"MyEvent" params:nil products:nil ignoreSubscriptionStatus:NO diff --git a/Examples/UIKit-ObjC/Superwall-UIKit-ObjC/Services/SSASuperwallService.m b/Examples/UIKit-ObjC/Superwall-UIKit-ObjC/Services/SSASuperwallService.m index 568d7e033..f2ca76724 100644 --- a/Examples/UIKit-ObjC/Superwall-UIKit-ObjC/Services/SSASuperwallService.m +++ b/Examples/UIKit-ObjC/Superwall-UIKit-ObjC/Services/SSASuperwallService.m @@ -66,16 +66,16 @@ + (SSASuperwallService *)sharedService { #pragma mark - Public Properties - (BOOL)isLoggedIn { - return Superwall.isLoggedIn; + return [Superwall sharedInstance].isLoggedIn; } - (nullable NSString *)name { - return Superwall.userAttributes[kUserAttributesFirstNameKey]; + return [Superwall sharedInstance].userAttributes[kUserAttributesFirstNameKey]; } - (void)setName:(nullable NSString *)name { id userAttributeFirstName = name ? : [NSNull null]; - [Superwall setUserAttributesDictionary:@{ kUserAttributesFirstNameKey : userAttributeFirstName }]; + [[Superwall sharedInstance] setUserAttributesDictionary:@{ kUserAttributesFirstNameKey : userAttributeFirstName }]; } #pragma mark - Public Methods @@ -89,7 +89,7 @@ - (void)initialize { } - (void)logInWithCompletion:(nullable void (^)(void))completion { - [Superwall logInUserId:kDemoUserId completionHandler:^(NSError * _Nullable error) { + [[Superwall sharedInstance] logInUserId:kDemoUserId completionHandler:^(NSError * _Nullable error) { switch (error.code) { case SWKIdentityErrorAlreadyLoggedIn: NSLog(@"The user is already logged in"); @@ -111,7 +111,7 @@ - (void)logInWithCompletion:(nullable void (^)(void))completion { } - (void)logOutWithCompletion:(nullable void (^)(void))completion { - [Superwall logOutWithCompletionHandler:^(NSError * _Nullable error) { + [[Superwall sharedInstance] logOutWithCompletionHandler:^(NSError * _Nullable error) { switch (error.code) { case SWKLogoutErrorNotLoggedIn: NSLog(@"The user is not logged in"); @@ -130,7 +130,7 @@ - (void)logOutWithCompletion:(nullable void (^)(void))completion { } - (void)handleDeepLinkWithURL:(NSURL *)URL { - [Superwall handleDeepLink:URL]; + [[Superwall sharedInstance] handleDeepLink:URL]; } #pragma mark - SuperwallDelegate @@ -161,101 +161,125 @@ - (void)didTrackSuperwallEventInfo:(SWKSuperwallEventInfo *)info { // Uncomment the following if you want to track the different analytics events received from the paywall: - // switch (info.event) { - // case SWKSuperwallEventFirstSeen: - // <#code#> - // break; - // case SWKSuperwallEventAppOpen: - // <#code#> - // break; - // case SWKSuperwallEventAppLaunch: - // <#code#> - // break; - // case SWKSuperwallEventAppInstall: - // <#code#> - // break; - // case SWKSuperwallEventSessionStart: - // <#code#> - // break; - // case SWKSuperwallEventAppClose: - // <#code#> - // break; - // case SWKSuperwallEventDeepLink: - // <#code#> - // break; - // case SWKSuperwallEventTriggerFire: - // <#code#> - // break; - // case SWKSuperwallEventPaywallOpen: - // <#code#> - // break; - // case SWKSuperwallEventPaywallClose: - // <#code#> - // break; - // case SWKSuperwallEventTransactionStart: - // <#code#> - // break; - // case SWKSuperwallEventTransactionFail: - // <#code#> - // break; - // case SWKSuperwallEventTransactionAbandon: - // <#code#> - // break; - // case SWKSuperwallEventTransactionComplete: - // <#code#> - // break; - // case SWKSuperwallEventSubscriptionStart: - // <#code#> - // break; - // case SWKSuperwallEventFreeTrialStart: - // <#code#> - // break; - // case SWKSuperwallEventTransactionRestore: - // <#code#> - // break; - // case SWKSuperwallEventTransactionTimeout: - // <#code#> - // break; - // case SWKSuperwallEventUserAttributes: - // <#code#> - // break; - // case SWKSuperwallEventNonRecurringProductPurchase: - // <#code#> - // break; - // case SWKSuperwallEventPaywallResponseLoadStart: - // <#code#> - // break; - // case SWKSuperwallEventPaywallResponseLoadNotFound: - // <#code#> - // break; - // case SWKSuperwallEventPaywallResponseLoadFail: - // <#code#> - // break; - // case SWKSuperwallEventPaywallResponseLoadComplete: - // <#code#> - // break; - // case SWKSuperwallEventPaywallWebviewLoadStart: - // <#code#> - // break; - // case SWKSuperwallEventPaywallWebviewLoadFail: - // <#code#> - // break; - // case SWKSuperwallEventPaywallWebviewLoadComplete: - // <#code#> - // break; - // case SWKSuperwallEventPaywallWebviewLoadTimeout: - // <#code#> - // break; - // case SWKSuperwallEventPaywallProductsLoadStart: - // <#code#> - // break; - // case SWKSuperwallEventPaywallProductsLoadFail: - // <#code#> - // break; - // case SWKSuperwallEventPaywallProductsLoadComplete: - // <#code#> - // break; - // } +// switch (info.event) { +// case SWKSuperwallEventFirstSeen: +// <#code#> +// break; +// case SWKSuperwallEventAppOpen: +// <#code#> +// break; +// case SWKSuperwallEventAppLaunch: +// <#code#> +// break; +// case SWKSuperwallEventAppInstall: +// <#code#> +// break; +// case SWKSuperwallEventSessionStart: +// <#code#> +// break; +// case SWKSuperwallEventAppClose: +// <#code#> +// break; +// case SWKSuperwallEventDeepLink: +// <#code#> +// break; +// case SWKSuperwallEventTriggerFire: +// <#code#> +// break; +// case SWKSuperwallEventPaywallOpen: +// <#code#> +// break; +// case SWKSuperwallEventPaywallClose: +// <#code#> +// break; +// case SWKSuperwallEventTransactionStart: +// <#code#> +// break; +// case SWKSuperwallEventTransactionFail: +// <#code#> +// break; +// case SWKSuperwallEventTransactionAbandon: +// <#code#> +// break; +// case SWKSuperwallEventTransactionComplete: +// <#code#> +// break; +// case SWKSuperwallEventSubscriptionStart: +// <#code#> +// break; +// case SWKSuperwallEventFreeTrialStart: +// <#code#> +// break; +// case SWKSuperwallEventTransactionRestore: +// <#code#> +// break; +// case SWKSuperwallEventTransactionTimeout: +// <#code#> +// break; +// case SWKSuperwallEventUserAttributes: +// <#code#> +// break; +// case SWKSuperwallEventNonRecurringProductPurchase: +// <#code#> +// break; +// case SWKSuperwallEventPaywallResponseLoadStart: +// <#code#> +// break; +// case SWKSuperwallEventPaywallResponseLoadNotFound: +// <#code#> +// break; +// case SWKSuperwallEventPaywallResponseLoadFail: +// <#code#> +// break; +// case SWKSuperwallEventPaywallResponseLoadComplete: +// <#code#> +// break; +// case SWKSuperwallEventPaywallWebviewLoadStart: +// <#code#> +// break; +// case SWKSuperwallEventPaywallWebviewLoadFail: +// <#code#> +// break; +// case SWKSuperwallEventPaywallWebviewLoadComplete: +// <#code#> +// break; +// case SWKSuperwallEventPaywallWebviewLoadTimeout: +// <#code#> +// break; +// case SWKSuperwallEventPaywallProductsLoadStart: +// <#code#> +// break; +// case SWKSuperwallEventPaywallProductsLoadFail: +// <#code#> +// break; +// case SWKSuperwallEventPaywallProductsLoadComplete: +// <#code#> +// break; +// case SWKSuperwallEventPaywallPresentationFailInHoldout: +// <#code#> +// break; +// case SWKSuperwallEventPaywallPresentationFailNoPresenter: +// <#code#> +// break; +// case SWKSuperwallEventPaywallPresentationFailAlreadyPresented: +// <#code#> +// break; +// case SWKSuperwallEventPaywallPresentationFailDebuggerLaunched: +// <#code#> +// break; +// case SWKSuperwallEventPaywallPresentationFailNoRuleMatch: +// <#code#> +// break; +// case SWKSuperwallEventPaywallPresentationFailEventNotFound: +// <#code#> +// break; +// case SWKSuperwallEventPaywallPresentationFailUserIsSubscribed: +// <#code#> +// break; +// case SWKSuperwallEventPaywallPresentationFailNoPaywallViewController: +// <#code#> +// break +// } } @end diff --git a/Examples/UIKit-Swift/README.md b/Examples/UIKit-Swift/README.md index 55d512389..ba9647127 100644 --- a/Examples/UIKit-Swift/README.md +++ b/Examples/UIKit-Swift/README.md @@ -50,9 +50,9 @@ The SDK sends back events received from the paywall via the delegate methods in ## Logging In -On the welcome screen, enter your name in the **text field**This saves to the Superwall user attributes using [Superwall.setUserAttributes(_:)](Superwall-UIKit-Swift/Services/SuperwallService.swift#L63). You don't need to set user attributes, but it can be useful if you want to create a rule to present a paywall based on a specific attribute you've set. You can also recall user attributes on your paywall to personalise the messaging. +On the welcome screen, enter your name in the **text field**This saves to the Superwall user attributes using [Superwall.shared.setUserAttributes(_:)](Superwall-UIKit-Swift/Services/SuperwallService.swift#L63). You don't need to set user attributes, but it can be useful if you want to create a rule to present a paywall based on a specific attribute you've set. You can also recall user attributes on your paywall to personalise the messaging. -Tap **Log In**. This logs the user in to Superwall (with a hardcoded userId that we've set), retrieving any paywalls that have already been assigned to them. If you were to create a new account you'd use `Superwall.createAccount(userId:)` instead. +Tap **Log In**. This logs the user in to Superwall (with a hardcoded userId that we've set), retrieving any paywalls that have already been assigned to them. If you were to create a new account you'd use `Superwall.shared.createAccount(userId:)` instead. You'll see an overview screen: @@ -69,7 +69,7 @@ On the [Superwall Dashboard](https://superwall.com/dashboard) you add this event When an event is tracked, SuperwallKit evaluates the rules associated with it to determine whether or not to show a paywall. Note that if the delegate method [isUserSubscribed()](Superwall-UIKit-Swift/SuperwallService.swift#L82) returns `true`, a paywall will not show by default. -By calling [Superwall.track(event:params:paywallOverrides:paywallHandler:)](Superwall-UIKit-Swift/TrackEventViewController.swift#L57), you present a paywall in response to the event. For this app, the event is called "MyEvent". +By calling [Superwall.shared.track(event:params:paywallOverrides:paywallHandler:)](Superwall-UIKit-Swift/TrackEventViewController.swift#L57), you present a paywall in response to the event. For this app, the event is called "MyEvent". On screen you'll see some explanatory text and a button that tracks an event: diff --git a/Examples/UIKit-Swift/Superwall-UIKit-Swift/Services/StoreKitService.swift b/Examples/UIKit-Swift/Superwall-UIKit-Swift/Services/StoreKitService.swift index 11b0ae35a..3ddcfae89 100644 --- a/Examples/UIKit-Swift/Superwall-UIKit-Swift/Services/StoreKitService.swift +++ b/Examples/UIKit-Swift/Superwall-UIKit-Swift/Services/StoreKitService.swift @@ -72,15 +72,15 @@ final class StoreKitService: NSObject, ObservableObject { // MARK: - SKPaymentTransactionObserver extension StoreKitService: SKPaymentTransactionObserver { func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) { - restoreCompletion?(true) - } + restoreCompletion?(true) + } - func paymentQueue( - _ queue: SKPaymentQueue, - restoreCompletedTransactionsFailedWithError error: Error - ) { - restoreCompletion?(false) - } + func paymentQueue( + _ queue: SKPaymentQueue, + restoreCompletedTransactionsFailedWithError error: Error + ) { + restoreCompletion?(false) + } func paymentQueue( _ queue: SKPaymentQueue, diff --git a/Examples/UIKit-Swift/Superwall-UIKit-Swift/Services/SuperwallService.swift b/Examples/UIKit-Swift/Superwall-UIKit-Swift/Services/SuperwallService.swift index 1ff31ffc0..8ce209302 100644 --- a/Examples/UIKit-Swift/Superwall-UIKit-Swift/Services/SuperwallService.swift +++ b/Examples/UIKit-Swift/Superwall-UIKit-Swift/Services/SuperwallService.swift @@ -14,7 +14,7 @@ final class SuperwallService { #warning("For your own app you will need to use your own API key, available from the Superwall Dashboard") private static let apiKey = "pk_e6bd9bd73182afb33e95ffdf997b9df74a45e1b5b46ed9c9" static var name: String { - return Superwall.userAttributes["firstName"] as? String ?? "" + return Superwall.shared.userAttributes["firstName"] as? String ?? "" } static func configure() { @@ -34,7 +34,7 @@ final class SuperwallService { static func logIn() async { do { - try await Superwall.logIn(userId: "abc") + try await Superwall.shared.logIn(userId: "abc") } catch let error as IdentityError { switch error { case .alreadyLoggedIn: @@ -49,7 +49,7 @@ final class SuperwallService { static func logOut() async { do { - try await Superwall.logOut() + try await Superwall.shared.logOut() } catch let error as LogoutError { switch error { case .notLoggedIn: @@ -61,11 +61,11 @@ final class SuperwallService { } static func handleDeepLink(_ url: URL) { - Superwall.handleDeepLink(url) + Superwall.shared.handleDeepLink(url) } static func setName(to name: String) { - Superwall.setUserAttributes(["firstName": name]) + Superwall.shared.setUserAttributes(["firstName": name]) } } diff --git a/Examples/UIKit-Swift/Superwall-UIKit-Swift/Superwall_UIKit-Swift-Products.storekit b/Examples/UIKit-Swift/Superwall-UIKit-Swift/Superwall_UIKit-Swift-Products.storekit index 9f40797ec..455f64c05 100644 --- a/Examples/UIKit-Swift/Superwall-UIKit-Swift/Superwall_UIKit-Swift-Products.storekit +++ b/Examples/UIKit-Swift/Superwall-UIKit-Swift/Superwall_UIKit-Swift-Products.storekit @@ -7,6 +7,7 @@ ], "settings" : { + "_billingIssuesEnabled" : false, "_compatibilityTimeRate" : 6, "_timeRate" : 15 }, diff --git a/Examples/UIKit-Swift/Superwall-UIKit-Swift/TrackEventViewController.swift b/Examples/UIKit-Swift/Superwall-UIKit-Swift/TrackEventViewController.swift index 6be083285..7ff51fd3e 100644 --- a/Examples/UIKit-Swift/Superwall-UIKit-Swift/TrackEventViewController.swift +++ b/Examples/UIKit-Swift/Superwall-UIKit-Swift/TrackEventViewController.swift @@ -56,7 +56,7 @@ final class TrackEventViewController: UIViewController { } @IBAction private func trackEvent() { - Superwall.track( + Superwall.shared.track( event: "MyEvent" ) { paywallState in switch paywallState { diff --git a/Sources/SuperwallKit/Analytics/App Session/AppSessionManager.swift b/Sources/SuperwallKit/Analytics/App Session/AppSessionManager.swift index a78b0b097..9cdc3a173 100644 --- a/Sources/SuperwallKit/Analytics/App Session/AppSessionManager.swift +++ b/Sources/SuperwallKit/Analytics/App Session/AppSessionManager.swift @@ -8,13 +8,17 @@ import UIKit import Combine +protocol AppManagerDelegate: AnyObject { + func didUpdateAppSession(_ appSession: AppSession) async +} + class AppSessionManager { var appSessionTimeout: Milliseconds? private(set) var appSession = AppSession() { didSet { Task { - await sessionEventsManager.updateAppSession(appSession) + await delegate.didUpdateAppSession(appSession) } } } @@ -24,29 +28,22 @@ class AppSessionManager { private unowned let configManager: ConfigManager private unowned let storage: Storage + private unowned let delegate: AppManagerDelegate - // swiftlint:disable implicitly_unwrapped_optional - private unowned var sessionEventsManager: SessionEventsManager! - // swiftlint:enable implicitly_unwrapped_optional - - /// **Note**: Remember to call `postInit` after init. init( configManager: ConfigManager, - storage: Storage + storage: Storage, + delegate: AppManagerDelegate ) { self.configManager = configManager self.storage = storage + self.delegate = delegate Task { await addActiveStateObservers() } listenForAppSessionTimeout() } - /// Initialises variables that can't be immediately init'd. - func postInit(sessionEventsManager: SessionEventsManager) { - self.sessionEventsManager = sessionEventsManager - } - // MARK: - Listeners @MainActor private func addActiveStateObservers() { @@ -89,7 +86,7 @@ class AppSessionManager { @objc private func applicationWillResignActive() { Task.detached(priority: .utility) { - await Superwall.track(InternalSuperwallEvent.AppClose()) + await Superwall.shared.track(InternalSuperwallEvent.AppClose()) } lastAppClose = Date() appSession.endAt = Date() @@ -101,7 +98,7 @@ class AppSessionManager { @objc private func applicationDidBecomeActive() { Task.detached(priority: .userInitiated) { - await Superwall.track(InternalSuperwallEvent.AppOpen()) + await Superwall.shared.track(InternalSuperwallEvent.AppOpen()) } sessionCouldRefresh() } @@ -124,7 +121,7 @@ class AppSessionManager { if didStartNewSession { appSession = AppSession() Task.detached(priority: .userInitiated) { - await Superwall.track(InternalSuperwallEvent.SessionStart()) + await Superwall.shared.track(InternalSuperwallEvent.SessionStart()) } } else { appSession.endAt = nil @@ -136,7 +133,7 @@ class AppSessionManager { return } Task.detached(priority: .userInitiated) { - await Superwall.track(InternalSuperwallEvent.AppLaunch()) + await Superwall.shared.track(InternalSuperwallEvent.AppLaunch()) } didTrackAppLaunch = true } diff --git a/Sources/SuperwallKit/Analytics/Internal Tracking/Trackable Events/TrackableSuperwallEvent.swift b/Sources/SuperwallKit/Analytics/Internal Tracking/Trackable Events/TrackableSuperwallEvent.swift index de523d298..44042e219 100644 --- a/Sources/SuperwallKit/Analytics/Internal Tracking/Trackable Events/TrackableSuperwallEvent.swift +++ b/Sources/SuperwallKit/Analytics/Internal Tracking/Trackable Events/TrackableSuperwallEvent.swift @@ -34,11 +34,11 @@ enum InternalSuperwallEvent { struct AppInstall: TrackableSuperwallEvent { let superwallEvent: SuperwallEvent = .appInstall - unowned let deviceHelper: DeviceHelper + let appInstalledAtString: String var customParameters: [String: Any] = [:] func getSuperwallParameters() async -> [String: Any] { return [ - "application_installed_at": deviceHelper.appInstalledAtString + "application_installed_at": appInstalledAtString ] } } diff --git a/Sources/SuperwallKit/Analytics/Internal Tracking/Tracking.swift b/Sources/SuperwallKit/Analytics/Internal Tracking/Tracking.swift index 0dd5cdbcd..1e2b8de2c 100644 --- a/Sources/SuperwallKit/Analytics/Internal Tracking/Tracking.swift +++ b/Sources/SuperwallKit/Analytics/Internal Tracking/Tracking.swift @@ -15,13 +15,13 @@ extension Superwall { /// - trackableEvent: The event you want to track. /// - customParameters: Any extra non-Superwall parameters that you want to track. @discardableResult - static func track(_ event: Trackable) async -> TrackingResult { + func track(_ event: Trackable) async -> TrackingResult { // Get parameters to be sent to the delegate and stored in an event. let eventCreatedAt = Date() let parameters = await TrackingLogic.processParameters( fromTrackableEvent: event, eventCreatedAt: eventCreatedAt, - appSessionId: shared.dependencyContainer.appSessionManager.appSession.id + appSessionId: dependencyContainer.appSessionManager.appSession.id ) // For a trackable superwall event, send params to delegate @@ -31,7 +31,7 @@ extension Superwall { params: parameters.delegateParams ) - await shared.dependencyContainer.delegateAdapter.didTrackSuperwallEventInfo(info) + await dependencyContainer.delegateAdapter.didTrackSuperwallEventInfo(info) Logger.debug( logLevel: .debug, @@ -46,12 +46,12 @@ extension Superwall { parameters: JSON(parameters.eventParams), createdAt: eventCreatedAt ) - await shared.dependencyContainer.queue.enqueue(event: eventData.jsonData) - shared.dependencyContainer.storage.coreDataManager.saveEventData(eventData) + await dependencyContainer.queue.enqueue(event: eventData.jsonData) + dependencyContainer.storage.coreDataManager.saveEventData(eventData) if event.canImplicitlyTriggerPaywall { - Task.detached { - await shared.handleImplicitTrigger( + Task.detached { [weak self] in + await self?.handleImplicitTrigger( forEvent: event, withData: eventData ) @@ -88,7 +88,7 @@ extension Superwall { switch outcome { case .deepLinkTrigger: if isPaywallPresented { - await Superwall.dismiss() + await dismiss() } let presentationRequest = dependencyContainer.makePresentationRequest( presentationInfo, @@ -97,8 +97,9 @@ extension Superwall { await internallyPresent(presentationRequest).asyncNoValue() case .triggerPaywall: // delay in case they are presenting a view controller alongside an event they are calling - let twoHundredMilliseconds = UInt64(200_000_000) - try? await Task.sleep(nanoseconds: twoHundredMilliseconds) + let milliseconds = 200 + let nanoseconds = UInt64(milliseconds * 1_000_000) + try? await Task.sleep(nanoseconds: nanoseconds) let presentationRequest = dependencyContainer.makePresentationRequest( presentationInfo, isPaywallPresented: isPaywallPresented diff --git a/Sources/SuperwallKit/Analytics/SessionEventsManager.swift b/Sources/SuperwallKit/Analytics/SessionEventsManager.swift index 2876c75da..594f102ec 100644 --- a/Sources/SuperwallKit/Analytics/SessionEventsManager.swift +++ b/Sources/SuperwallKit/Analytics/SessionEventsManager.swift @@ -9,9 +9,7 @@ import UIKit import Combine protocol SessionEventsDelegate: AnyObject { - // swiftlint:disable implicitly_unwrapped_optional - var triggerSession: TriggerSessionManager! { get } - // swiftlint:enable implicitly_unwrapped_optional + var triggerSession: TriggerSessionManager { get } func enqueue(_ triggerSession: TriggerSession) async func enqueue(_ triggerSessions: [TriggerSession]) async @@ -19,10 +17,8 @@ protocol SessionEventsDelegate: AnyObject { } class SessionEventsManager { - // swiftlint:disable implicitly_unwrapped_optional /// The trigger session manager. - var triggerSession: TriggerSessionManager! - // swiftlint:enable implicitly_unwrapped_optional + lazy var triggerSession = factory.makeTriggerSessionManager() /// A queue of trigger session events that get sent to the server. private let queue: SessionEnqueuable @@ -32,9 +28,8 @@ class SessionEventsManager { private unowned let network: Network private unowned let storage: Storage private unowned let configManager: ConfigManager - private let factory: TriggerSessionManagerFactory + private unowned let factory: TriggerSessionManagerFactory - /// Remember to call postInit init( queue: SessionEnqueuable, storage: Storage, @@ -53,10 +48,6 @@ class SessionEventsManager { } } - func postInit() { - self.triggerSession = factory.makeTriggerSessionManager() - } - /// Gets the last 20 cached trigger sessions and transactions from the last time the app was terminated, /// sends them back to the server, then clears cache. private func postCachedSessionEvents() async { diff --git a/Sources/SuperwallKit/Analytics/Superwall Event/SuperwallEvent.swift b/Sources/SuperwallKit/Analytics/Superwall Event/SuperwallEvent.swift index dfbf92fad..6af3b232f 100644 --- a/Sources/SuperwallKit/Analytics/Superwall Event/SuperwallEvent.swift +++ b/Sources/SuperwallKit/Analytics/Superwall Event/SuperwallEvent.swift @@ -27,7 +27,7 @@ public enum SuperwallEvent { /// The raw value of this event can be added to a campaign to trigger a paywall. case appInstall - /// When the app is opened at least an hour since last ``SuperwallEvent/appClose``. + /// When the app is opened at least an hour since last ``appClose``. /// /// The raw value of this event can be added to a campaign to trigger a paywall. case sessionStart diff --git a/Sources/SuperwallKit/Analytics/Trigger Session Manager/Model/Trigger/TriggerSessionExperiment.swift b/Sources/SuperwallKit/Analytics/Trigger Session Manager/Model/Trigger/TriggerSessionExperiment.swift deleted file mode 100644 index 82b4dee2f..000000000 --- a/Sources/SuperwallKit/Analytics/Trigger Session Manager/Model/Trigger/TriggerSessionExperiment.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// File.swift -// -// -// Created by Yusuf Tör on 28/04/2022. -// -/* -import Foundation - -extension TriggerSession.Trigger { - struct Experiment: Codable { - /// The ID of the experiment in the database. - var id: String - - /// The database ID of the group the trigger is in - let groupId: String - - struct Variant: Codable { - /// The variant id - let id: String - - enum VariantType: String, Codable { - case holdout = "HOLDOUT" - case treatment = "TREATMENT" - } - /// Whether the user is assigned to a holdout variant - let type: VariantType - } - /// The variant of the paywall within the experiment. - let variant: Variant - - enum CodingKeys: String, CodingKey { - case id = "experiment_id" - case groupId = "trigger_experiment_group_id" - case variantId = "variant_id" - case variantType = "variant_type" - } - - init( - id: String, - groupId: String, - variant: Variant - ) { - self.id = id - self.groupId = groupId - self.variant = variant - } - - init(from decoder: Decoder) throws { - let values = try decoder.container(keyedBy: CodingKeys.self) - id = try values.decode(String.self, forKey: .id) - groupId = try values.decode(String.self, forKey: .groupId) - - let id = try values.decode(String.self, forKey: .variantId) - let type = try values.decode(Variant.VariantType.self, forKey: .variantType) - variant = Variant(id: id, type: type) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(id, forKey: .id) - try container.encode(groupId, forKey: .groupId) - try container.encode(variant.id, forKey: .variantId) - try container.encode(variant.type, forKey: .variantType) - } - } -} -*/ diff --git a/Sources/SuperwallKit/Analytics/Trigger Session Manager/Model/Trigger/TriggerSessionTrigger.swift b/Sources/SuperwallKit/Analytics/Trigger Session Manager/Model/TriggerSessionTrigger.swift similarity index 100% rename from Sources/SuperwallKit/Analytics/Trigger Session Manager/Model/Trigger/TriggerSessionTrigger.swift rename to Sources/SuperwallKit/Analytics/Trigger Session Manager/Model/TriggerSessionTrigger.swift diff --git a/Sources/SuperwallKit/Analytics/Trigger Session Manager/TriggerSessionManager.swift b/Sources/SuperwallKit/Analytics/Trigger Session Manager/TriggerSessionManager.swift index 1d3f6874d..7c56b11f4 100644 --- a/Sources/SuperwallKit/Analytics/Trigger Session Manager/TriggerSessionManager.swift +++ b/Sources/SuperwallKit/Analytics/Trigger Session Manager/TriggerSessionManager.swift @@ -134,7 +134,7 @@ actor TriggerSessionManager { on presentingViewController: UIViewController? = nil, paywall: Paywall? = nil, triggerResult: TriggerResult?, - trackEvent: (Trackable) async -> TrackingResult = Superwall.track + trackEvent: (Trackable) async -> TrackingResult = Superwall.shared.track ) async { guard let eventName = presentationInfo.eventName else { // The paywall is being presented by identifier, which is what the debugger uses, diff --git a/Sources/SuperwallKit/Config/ConfigManager.swift b/Sources/SuperwallKit/Config/ConfigManager.swift index 872c46922..43ef45cc0 100644 --- a/Sources/SuperwallKit/Config/ConfigManager.swift +++ b/Sources/SuperwallKit/Config/ConfigManager.swift @@ -12,7 +12,7 @@ class ConfigManager { @Published var config: Config? /// Options for configuring the SDK. - @Published var options = SuperwallOptions() + var options = SuperwallOptions() /// A dictionary of triggers by their event name. var triggersByEventName: [String: Trigger] = [:] @@ -26,19 +26,20 @@ class ConfigManager { private unowned let storage: Storage private unowned let network: Network private unowned let paywallManager: PaywallManager - // swiftlint:disable implicitly_unwrapped_optional - private unowned var deviceHelper: DeviceHelper! - // swiftlint:enable implicitly_unwrapped_optional - private let factory: RequestFactory - /// **NOTE**: Remember to call `postInit`after init. + private let factory: RequestFactory & DeviceInfoFactory + init( + options: SuperwallOptions?, storeKitManager: StoreKitManager, storage: Storage, network: Network, paywallManager: PaywallManager, - factory: RequestFactory + factory: RequestFactory & DeviceInfoFactory ) { + if let options = options { + self.options = options + } self.storeKitManager = storeKitManager self.storage = storage self.network = network @@ -46,13 +47,9 @@ class ConfigManager { self.factory = factory } - func postInit(deviceHelper: DeviceHelper) { - self.deviceHelper = deviceHelper - } - - func fetchConfiguration(requestId: String = UUID().uuidString) async { + func fetchConfiguration() async { do { - let config = try await network.getConfig(withRequestId: requestId) + let config = try await network.getConfig() Task { await sendProductsBack(from: config) } triggersByEventName = ConfigLogic.getTriggersByEventName(from: config.triggers) @@ -114,7 +111,7 @@ class ConfigManager { ) } - if Superwall.options.paywalls.shouldPreload { + if Superwall.shared.options.paywalls.shouldPreload { Task { await preloadAllPaywalls() } } } catch { @@ -143,10 +140,11 @@ class ConfigManager { /// Gets the paywall response from the static config, if the device locale starts with "en" and no more specific version can be found. func getStaticPaywall(withId paywallId: String?) -> Paywall? { + let deviceInfo = factory.makeDeviceInfo() return ConfigLogic.getStaticPaywall( withId: paywallId, config: config, - deviceLocale: deviceHelper.locale + deviceLocale: deviceInfo.locale ) } @@ -191,7 +189,7 @@ class ConfigManager { /// /// A developer can disable preloading of paywalls by setting ``SuperwallOptions/shouldPreloadPaywalls``. private func preloadPaywalls() async { - guard Superwall.options.paywalls.shouldPreload else { + guard Superwall.shared.options.paywalls.shouldPreload else { return } await preloadAllPaywalls() @@ -225,11 +223,12 @@ class ConfigManager { private func preloadPaywalls(withIdentifiers paywallIdentifiers: Set) { for identifier in paywallIdentifiers { Task { - let request = factory.makePaywallRequest(withId: identifier) - _ = try? await paywallManager.getPaywallViewController( - from: request, - cached: true + let request = factory.makePaywallRequest( + eventData: nil, + responseIdentifiers: .init(paywallId: identifier), + overrides: nil ) + _ = try? await paywallManager.getPaywallViewController(from: request) } } } @@ -239,11 +238,12 @@ class ConfigManager { guard config.featureFlags.enablePostback else { return } - let oneSecond = UInt64(1_000_000_000) - let nanosecondDelay = UInt64(config.postback.postbackDelay) * oneSecond + let milliseconds = 1000 + let nanoseconds = UInt64(milliseconds * 1_000_000) + let duration = UInt64(config.postback.postbackDelay) * nanoseconds do { - try await Task.sleep(nanoseconds: nanosecondDelay) + try await Task.sleep(nanoseconds: duration) let productIds = config.postback.productsToPostBack.map { $0.identifier } let products = try await storeKitManager.getProducts(withIds: productIds) diff --git a/Sources/SuperwallKit/Config/Options/PaywallOptions.swift b/Sources/SuperwallKit/Config/Options/PaywallOptions.swift index bfd1ec620..0d1f3997a 100644 --- a/Sources/SuperwallKit/Config/Options/PaywallOptions.swift +++ b/Sources/SuperwallKit/Config/Options/PaywallOptions.swift @@ -24,7 +24,9 @@ public final class PaywallOptions: NSObject { public var isExternalDataCollectionEnabled = true /// Defines the messaging of the alert presented to the user when restoring a transaction fails. - public struct RestoreFailed { + + @objc(SWKRestoreFailed) + public final class RestoreFailed: NSObject { /// The title of the alert presented to the user when restoring a transaction fails. Defaults to `No Subscription Found`. public var title = "No Subscription Found" @@ -56,7 +58,9 @@ public final class PaywallOptions: NSObject { public var automaticallyDismiss = true /// Defines the different types of views that can appear behind Apple's payment sheet during a transaction. - public enum TransactionBackgroundView: Sendable { + + @objc(SWKTransactionBackgroundView) + public enum TransactionBackgroundView: Int, Sendable { /// This shows your paywall background color overlayed with an activity indicator. case spinner } diff --git a/Sources/SuperwallKit/Config/Options/SuperwallOptions.swift b/Sources/SuperwallKit/Config/Options/SuperwallOptions.swift index a5195ead6..672f1dfce 100644 --- a/Sources/SuperwallKit/Config/Options/SuperwallOptions.swift +++ b/Sources/SuperwallKit/Config/Options/SuperwallOptions.swift @@ -16,7 +16,7 @@ public final class SuperwallOptions: NSObject { /// Configures the appearance and behaviour of paywalls. public var paywalls = PaywallOptions() - /// **WARNING**: Only use this enum to set `Superwall.networkEnvironment` if told so explicitly by the Superwall team. + /// **WARNING**: Only use this enum to set ``SuperwallOptions/networkEnvironment-swift.property`` if told so explicitly by the Superwall team. public enum NetworkEnvironment { /// Default: Uses the standard latest environment. case release @@ -63,7 +63,7 @@ public final class SuperwallOptions: NSObject { public var isGameControllerEnabled = false /// Configuration for printing to the console. - public struct Logging { + public final class Logging: NSObject { /// Defines the minimum log level to print to the console. Defaults to `warn`. public var level: LogLevel? = .warn diff --git a/Sources/SuperwallKit/Debug/DebugManager.swift b/Sources/SuperwallKit/Debug/DebugManager.swift index 57da87990..cf5c8dca5 100644 --- a/Sources/SuperwallKit/Debug/DebugManager.swift +++ b/Sources/SuperwallKit/Debug/DebugManager.swift @@ -14,7 +14,7 @@ final class DebugManager { var isDebuggerLaunched = false private unowned let storage: Storage - private let factory: ViewControllerFactory + private unowned let factory: ViewControllerFactory init( storage: Storage, @@ -55,18 +55,20 @@ final class DebugManager { /// Launches the debugger for you to preview paywalls. /// - /// If you call `Superwall.handleDeepLink(url)` from `application(_:, open:, options:)` in your `AppDelegate`, this function is called automatically after scanning your debug QR code in Superwall's web dashboard. + /// If you call ``Superwall/handleDeepLink(_:)`` from `application(_:open:options:)` in your + /// `AppDelegate`, this function is called automatically after scanning your debug QR code in Superwall's web dashboard. /// /// Remember to add your URL scheme in settings for QR code scanning to work. @MainActor func launchDebugger(withPaywallId paywallDatabaseId: String? = nil) async { if Superwall.shared.isPaywallPresented { - await Superwall.dismiss() + await Superwall.shared.dismiss() await launchDebugger(withPaywallId: paywallDatabaseId) } else { if viewController == nil { - let twoHundredMilliseconds = UInt64(200_000_000) - try? await Task.sleep(nanoseconds: twoHundredMilliseconds) + let milliseconds = 200 + let nanoseconds = UInt64(milliseconds * 1_000_000) + try? await Task.sleep(nanoseconds: nanoseconds) await presentDebugger(withPaywallId: paywallDatabaseId) } else { await closeDebugger(animated: true) diff --git a/Sources/SuperwallKit/Debug/DebugViewController.swift b/Sources/SuperwallKit/Debug/DebugViewController.swift index 50b911898..910c564ed 100644 --- a/Sources/SuperwallKit/Debug/DebugViewController.swift +++ b/Sources/SuperwallKit/Debug/DebugViewController.swift @@ -124,8 +124,8 @@ final class DebugViewController: UIViewController { private unowned let network: Network private unowned let paywallRequestManager: PaywallRequestManager private unowned let paywallManager: PaywallManager - private unowned let localizationManager: LocalizationManager private unowned let debugManager: DebugManager + private let localizationManager: LocalizationManager private let factory: RequestFactory & ViewControllerFactory init( @@ -238,7 +238,11 @@ final class DebugViewController: UIViewController { } do { - let request = factory.makePaywallRequest(withId: paywallId) + let request = factory.makePaywallRequest( + eventData: nil, + responseIdentifiers: .init(paywallId: paywallId), + overrides: nil + ) var paywall = try await paywallRequestManager.getPaywall(from: request) let productVariables = await storeKitManager.getProductVariables(for: paywall) @@ -421,7 +425,6 @@ final class DebugViewController: UIViewController { isPaywallPresented: Superwall.shared.isPaywallPresented ) - cancellable = Superwall.shared .internallyPresent(presentationRequest) .sink { state in diff --git a/Sources/SuperwallKit/Debug/Localizations/LocalizationManager.swift b/Sources/SuperwallKit/Debug/Localizations/LocalizationManager.swift index 038c536e1..02b121084 100644 --- a/Sources/SuperwallKit/Debug/Localizations/LocalizationManager.swift +++ b/Sources/SuperwallKit/Debug/Localizations/LocalizationManager.swift @@ -10,17 +10,17 @@ import Foundation final class LocalizationManager { let popularLocales = ["de_DE", "es_US"] var selectedLocale: String? + let localizationGroupings: [LocalizationGrouping] - lazy var localizationGroupings: [LocalizationGrouping] = { - let localeIds = Locale.availableIdentifiers + init() { + let localeIds = Locale.availableIdentifiers let sortedLocalizations = LocalizationLogic.getSortedLocalizations( forLocales: localeIds, popularLocales: popularLocales ) - let groupings = LocalizationLogic.getGroupings(for: sortedLocalizations) - - return groupings - }() + let localizationGroupings = LocalizationLogic.getGroupings(for: sortedLocalizations) + self.localizationGroupings = localizationGroupings + } func localizationGroupings(forSearchTerm searchTerm: String?) -> [LocalizationGrouping] { let query = searchTerm?.lowercased() ?? "" diff --git a/Sources/SuperwallKit/Delegate/PurchaseResult.swift b/Sources/SuperwallKit/Delegate/PurchaseResult.swift index 12dc3379a..98c040767 100644 --- a/Sources/SuperwallKit/Delegate/PurchaseResult.swift +++ b/Sources/SuperwallKit/Delegate/PurchaseResult.swift @@ -19,7 +19,7 @@ enum InternalPurchaseResult { /// /// When implementing the ``SubscriptionController/purchase(product:)`` delegate /// method, all cases should be considered. -public enum PurchaseResult: Sendable { +public enum PurchaseResult: Sendable, Equatable { /// The purchase was cancelled. /// /// In StoreKit 1, you can detect this by switching over the error code enum from the `.failed` @@ -50,13 +50,26 @@ public enum PurchaseResult: Sendable { /// /// Send the `Error` back to Superwall to alert the user. case failed(Error) + + public static func == (lhs: PurchaseResult, rhs: PurchaseResult) -> Bool { + switch (lhs, rhs) { + case (.cancelled, .cancelled), + (.purchased, .purchased), + (.pending, .pending): + return true + case let (.failed(error), .failed(error2)): + return error.localizedDescription == error2.localizedDescription + default: + return false + } + } } // MARK: - Objective-C Only /// An Objective-C-only enum that defines the possible outcomes of attempting to purchase a product. @objc(SWKPurchaseResult) -public enum PurchaseResultObjc: Int, Sendable { +public enum PurchaseResultObjc: Int, Sendable, Equatable { /// The purchase was cancelled. /// /// In StoreKit 1, you can detect this by switching over the error code from the `.failed` @@ -82,7 +95,7 @@ public enum PurchaseResultObjc: Int, Sendable { /// The purchase failed for a reason other than the user cancelling or the payment pending. /// - /// Send the `Error` back in the ``SuperwallDelegateObjc/purchase(product:completion:)`` + /// Send the `Error` back in the ``SubscriptionControllerObjc/purchase(product:completion:)`` /// completion block to Superwall to alert the user. case failed } diff --git a/Sources/SuperwallKit/Delegate/SuperwallDelegateAdapter.swift b/Sources/SuperwallKit/Delegate/SuperwallDelegateAdapter.swift index 3035f025d..14376e042 100644 --- a/Sources/SuperwallKit/Delegate/SuperwallDelegateAdapter.swift +++ b/Sources/SuperwallKit/Delegate/SuperwallDelegateAdapter.swift @@ -20,7 +20,7 @@ final class SuperwallDelegateAdapter { /// Called on init of the Superwall instance via ``SuperwallKit/Superwall/configure(apiKey:delegate:options:)-7doe5``. /// /// We check to see if the delegates being set are non-nil because they may have been set - /// separately to the initial Superwall.config function. + /// separately to the initial ``Superwall/configure(apiKey:delegate:options:)-65jyx`` function. init( swiftDelegate: SuperwallDelegate?, objcDelegate: SuperwallDelegateObjc? diff --git a/Sources/SuperwallKit/Dependencies/DependencyContainer.swift b/Sources/SuperwallKit/Dependencies/DependencyContainer.swift index 090bf9195..2c2bd2444 100644 --- a/Sources/SuperwallKit/Dependencies/DependencyContainer.swift +++ b/Sources/SuperwallKit/Dependencies/DependencyContainer.swift @@ -4,7 +4,6 @@ // // Created by Yusuf Tör on 23/12/2022. // -// swiftlint:disable function_body_length import UIKit @@ -18,127 +17,121 @@ import UIKit /// /// Idea taken from: [swiftbysundell.com](https://www.swiftbysundell.com/articles/dependency-injection-using-factories-in-swift/) final class DependencyContainer { - // swiftlint:disable implicitly_unwrapped_optional - var configManager: ConfigManager! - var identityManager: IdentityManager! - var storeKitManager: StoreKitManager! - var appSessionManager: AppSessionManager! - var sessionEventsManager: SessionEventsManager! - var storage: Storage! - var network: Network! - var paywallManager: PaywallManager! - var paywallRequestManager: PaywallRequestManager! - var deviceHelper: DeviceHelper! - var localizationManager: LocalizationManager! - var queue: EventsQueue! - var debugManager: DebugManager! - var api: Api! - var transactionManager: TransactionManager! - var restorationHandler: RestorationHandler! - var delegateAdapter: SuperwallDelegateAdapter! - // swiftlint:enable implicitly_unwrapped_optional + lazy var storeKitManager = StoreKitManager(factory: self) + let delegateAdapter: SuperwallDelegateAdapter + lazy var localizationManager = LocalizationManager() + lazy var storage = Storage(factory: self) + lazy var network = Network(factory: self) + lazy var paywallRequestManager = PaywallRequestManager( + storeKitManager: storeKitManager, + factory: self + ) + lazy var paywallManager = PaywallManager( + factory: self, + paywallRequestManager: paywallRequestManager + ) + lazy var configManager = ConfigManager( + options: options, + storeKitManager: storeKitManager, + storage: storage, + network: network, + paywallManager: paywallManager, + factory: self + ) + lazy var api = Api(networkEnvironment: configManager.options.networkEnvironment) + lazy var deviceHelper = DeviceHelper( + api: api, + storage: storage, + localizationManager: localizationManager, + factory: self + ) + lazy var queue = EventsQueue( + network: network, + configManager: configManager + ) + lazy var appSessionManager = AppSessionManager( + configManager: configManager, + storage: storage, + delegate: self + ) + lazy var identityManager = IdentityManager( + deviceHelper: deviceHelper, + storage: storage, + configManager: configManager + ) + lazy var sessionEventsManager = SessionEventsManager( + queue: SessionEventsQueue( + storage: storage, + network: network, + configManager: configManager + ), + storage: storage, + network: network, + configManager: configManager, + factory: self + ) + lazy var debugManager = DebugManager( + storage: storage, + factory: self + ) + lazy var transactionManager = TransactionManager( + storeKitManager: storeKitManager, + sessionEventsManager: sessionEventsManager + ) + lazy var restorationManager = RestorationManager( + storeKitManager: storeKitManager, + sessionEventsManager: sessionEventsManager + ) + private let options: SuperwallOptions? init( - apiKey: String, swiftDelegate: SuperwallDelegate? = nil, objcDelegate: SuperwallDelegateObjc? = nil, options: SuperwallOptions? = nil ) { - storeKitManager = StoreKitManager(factory: self) delegateAdapter = SuperwallDelegateAdapter( swiftDelegate: swiftDelegate, objcDelegate: objcDelegate ) - localizationManager = LocalizationManager() - storage = Storage() - network = Network(factory: self) - - paywallRequestManager = PaywallRequestManager( - storeKitManager: storeKitManager - ) - paywallManager = PaywallManager( - factory: self, - paywallRequestManager: paywallRequestManager - ) - - configManager = ConfigManager( - storeKitManager: storeKitManager, - storage: storage, - network: network, - paywallManager: paywallManager, - factory: self - ) - - if let options = options { - configManager.options = options - } - - api = Api(configManager: configManager) + self.options = options - deviceHelper = DeviceHelper( - api: api, - storage: storage, - localizationManager: localizationManager - ) - - queue = EventsQueue( - network: network, - configManager: configManager - ) - - appSessionManager = AppSessionManager( - configManager: configManager, - storage: storage - ) - - identityManager = IdentityManager( - deviceHelper: deviceHelper, - storage: storage, - configManager: configManager - ) + // Make sure this lazy var is initialised immediately + // due to needing session tracking. + _ = appSessionManager + } +} - sessionEventsManager = SessionEventsManager( - queue: SessionEventsQueue( - storage: storage, - network: network, - configManager: configManager - ), - storage: storage, - network: network, - configManager: configManager, - factory: self +// MARK: - IdentityInfoFactory +extension DependencyContainer: IdentityInfoFactory { + func makeIdentityInfo() -> IdentityInfo { + return IdentityInfo( + aliasId: identityManager.aliasId, + appUserId: identityManager.appUserId ) + } +} - debugManager = DebugManager( - storage: storage, - factory: self - ) +// MARK: - AppManagerDelegate +extension DependencyContainer: AppManagerDelegate { + func didUpdateAppSession(_ appSession: AppSession) async { + await sessionEventsManager.updateAppSession(appSession) + } +} - transactionManager = TransactionManager( - storeKitManager: storeKitManager, - sessionEventsManager: sessionEventsManager - ) +// MARK: - CacheFactory +extension DependencyContainer: CacheFactory { + func makeCache() -> PaywallCache { + return PaywallCache(deviceLocaleString: deviceHelper.locale) + } +} - restorationHandler = RestorationHandler( - storeKitManager: storeKitManager, - sessionEventsManager: sessionEventsManager +// MARK: - DeviceInfofactory +extension DependencyContainer: DeviceInfoFactory { + func makeDeviceInfo() -> DeviceInfo { + return DeviceInfo( + appInstalledAtString: deviceHelper.appInstalledAtString, + locale: deviceHelper.locale ) - - // MARK: Post Init - - // We have to call postInit on some of the objects to avoid - // retain cycles. - storeKitManager.postInit() - sessionEventsManager.postInit() - storage.postInit(deviceHelper: deviceHelper) - deviceHelper.postInit(identityManager: identityManager) - configManager.postInit(deviceHelper: deviceHelper) - paywallManager.postInit(deviceHelper: deviceHelper) - appSessionManager.postInit(sessionEventsManager: sessionEventsManager) - - Task { - await paywallRequestManager.postInit(deviceHelper: deviceHelper) - } } } @@ -201,16 +194,15 @@ extension DependencyContainer: VariablesFactory { // MARK: - PaywallRequestFactory extension DependencyContainer: RequestFactory { - func makePaywallRequest(withId paywallId: String) -> PaywallRequest { + func makePaywallRequest( + eventData: EventData? = nil, + responseIdentifiers: ResponseIdentifiers, + overrides: PaywallRequest.Overrides? = nil + ) -> PaywallRequest { return PaywallRequest( - responseIdentifiers: .init(paywallId: paywallId), - injections: .init( - sessionEventsManager: sessionEventsManager, - storeKitManager: storeKitManager, - configManager: configManager, - network: network, - debugManager: debugManager - ) + eventData: eventData, + responseIdentifiers: responseIdentifiers, + dependencyContainer: self ) } @@ -225,20 +217,13 @@ extension DependencyContainer: RequestFactory { return PresentationRequest( presentationInfo: presentationInfo, presentingViewController: presentingViewController, - injections: .init( - configManager: configManager, - storage: storage, - sessionEventsManager: sessionEventsManager, - paywallManager: paywallManager, - storeKitManager: storeKitManager, - network: network, - debugManager: debugManager, - identityManager: identityManager, - deviceHelper: deviceHelper, + paywallOverrides: paywallOverrides, + flags: .init( isDebuggerLaunched: isDebuggerLaunched ?? debugManager.isDebuggerLaunched, isUserSubscribed: isUserSubscribed ?? storeKitManager.coordinator.subscriptionStatusHandler.isSubscribed(), isPaywallPresented: isPaywallPresented - ) + ), + dependencyContainer: self ) } } @@ -247,8 +232,7 @@ extension DependencyContainer: RequestFactory { extension DependencyContainer: ApiFactory { func makeHeaders( fromRequest request: URLRequest, - requestId: String, - forDebugging isForDebugging: Bool + requestId: String ) -> [String: String] { let auth = "Bearer \(storage.apiKey)" let headers = [ @@ -257,6 +241,7 @@ extension DependencyContainer: ApiFactory { "X-Platform-Environment": "SDK", "X-App-User-ID": identityManager.appUserId ?? "", "X-Alias-ID": identityManager.aliasId, + "X-URL-Scheme": deviceHelper.urlScheme, "X-Vendor-ID": deviceHelper.vendorId, "X-App-Version": deviceHelper.appVersion, "X-OS-Version": deviceHelper.osVersion, @@ -281,6 +266,19 @@ extension DependencyContainer: ApiFactory { } } +// MARK: - Rule Params +extension DependencyContainer: RuleAttributesFactory { + func makeRuleAttributes() -> RuleAttributes { + var userAttributes = identityManager.userAttributes + userAttributes["isLoggedIn"] = identityManager.isLoggedIn + + return RuleAttributes( + user: userAttributes, + device: deviceHelper.templateDevice.toDictionary() + ) + } +} + // MARK: - TriggerSessionManager extension DependencyContainer: TriggerSessionManagerFactory { func makeTriggerSessionManager() -> TriggerSessionManager { diff --git a/Sources/SuperwallKit/Dependencies/FactoryProtocols.swift b/Sources/SuperwallKit/Dependencies/FactoryProtocols.swift index 4ba6a9f30..beb3d635d 100644 --- a/Sources/SuperwallKit/Dependencies/FactoryProtocols.swift +++ b/Sources/SuperwallKit/Dependencies/FactoryProtocols.swift @@ -7,21 +7,29 @@ import UIKit -protocol ViewControllerFactory { +protocol ViewControllerFactory: AnyObject { @MainActor func makePaywallViewController(for paywall: Paywall) -> PaywallViewController func makeDebugViewController(withDatabaseId id: String?) -> DebugViewController } -protocol VariablesFactory { +protocol CacheFactory: AnyObject { + func makeCache() -> PaywallCache +} + +protocol VariablesFactory: AnyObject { func makeJsonVariables( productVariables: [ProductVariable]?, params: JSON? ) -> JSON } -protocol RequestFactory { - func makePaywallRequest(withId paywallId: String) -> PaywallRequest +protocol RequestFactory: AnyObject { + func makePaywallRequest( + eventData: EventData?, + responseIdentifiers: ResponseIdentifiers, + overrides: PaywallRequest.Overrides? + ) -> PaywallRequest func makePresentationRequest( _ presentationInfo: PresentationInfo, @@ -33,36 +41,45 @@ protocol RequestFactory { ) -> PresentationRequest } -protocol TriggerSessionManagerFactory { +protocol RuleAttributesFactory: AnyObject { + func makeRuleAttributes() -> RuleAttributes +} + +protocol TriggerSessionManagerFactory: AnyObject { func makeTriggerSessionManager() -> TriggerSessionManager } -protocol StoreKitCoordinatorFactory { +protocol StoreKitCoordinatorFactory: AnyObject { func makeStoreKitCoordinator() -> StoreKitCoordinator } -protocol ApiFactory { - // swiftlint:disable implicitly_unwrapped_optional +protocol IdentityInfoFactory: AnyObject { + func makeIdentityInfo() -> IdentityInfo +} + +protocol DeviceInfoFactory: AnyObject { + func makeDeviceInfo() -> DeviceInfo +} + +protocol ApiFactory: AnyObject { // TODO: Think of an alternative way such that we don't need to do this: - var api: Api! { get } - var storage: Storage! { get } - var deviceHelper: DeviceHelper! { get } - var configManager: ConfigManager! { get } - var identityManager: IdentityManager! { get } - // swiftlint:enable implicitly_unwrapped_optional + var api: Api { get } + var storage: Storage { get } + var deviceHelper: DeviceHelper { get } + var configManager: ConfigManager { get } + var identityManager: IdentityManager { get } func makeHeaders( fromRequest request: URLRequest, - requestId: String, - forDebugging isForDebugging: Bool + requestId: String ) -> [String: String] } -protocol ProductPurchaserFactory { +protocol ProductPurchaserFactory: AnyObject { func makeSK1ProductPurchaser() -> ProductPurchaserSK1 } -protocol StoreTransactionFactory { +protocol StoreTransactionFactory: AnyObject { func makeStoreTransaction(from transaction: SK1Transaction) async -> StoreTransaction @available(iOS 15.0, tvOS 15.0, watchOS 8.0, *) diff --git a/Sources/SuperwallKit/Documentation.docc/Extensions/SuperwallExtension.md b/Sources/SuperwallKit/Documentation.docc/Extensions/SuperwallExtension.md index ebc950c5f..8beb9023b 100644 --- a/Sources/SuperwallKit/Documentation.docc/Extensions/SuperwallExtension.md +++ b/Sources/SuperwallKit/Documentation.docc/Extensions/SuperwallExtension.md @@ -2,7 +2,7 @@ ## Overview -The ``SuperwallKit/Superwall`` class is used to access all the features of the SDK. Before using any of the features, you must call ``SuperwallKit/Superwall/configure(apiKey:delegate:options:)-65jyx`` to configure the SDK. +The ``Superwall`` class is used to access all the features of the SDK. Before using any of the features, you must call ``Superwall/configure(apiKey:delegate:options:)-65jyx`` to configure the SDK. ## Topics @@ -13,6 +13,7 @@ The ``SuperwallKit/Superwall`` class is used to access all the features of the S - - ``configure(apiKey:delegate:options:)-65jyx`` - ``configure(apiKey:delegate:options:)-48l7e`` +- ``shared`` - ``SuperwallDelegate`` - ``SuperwallDelegateObjc`` - ``delegate`` @@ -69,6 +70,7 @@ The ``SuperwallKit/Superwall`` class is used to access all the features of the S ### Logging +- ``logLevel`` - ``SuperwallDelegate/handleLog(level:scope:message:info:error:)-9kmai`` - ``LogLevel`` - ``LogScope`` diff --git a/Sources/SuperwallKit/Documentation.docc/GameControllerSupport.md b/Sources/SuperwallKit/Documentation.docc/GameControllerSupport.md index eb852d456..8aebed650 100644 --- a/Sources/SuperwallKit/Documentation.docc/GameControllerSupport.md +++ b/Sources/SuperwallKit/Documentation.docc/GameControllerSupport.md @@ -6,12 +6,12 @@ Sending values from the game controller to the SDK SuperwallKit supports Game Controller input. -To forward events to your paywall, simply call ``SuperwallKit/Superwall/gamepadValueChanged(gamepad:element:)`` from your own gamepad's valueChanged handler: +To forward events to your paywall, simply call ``Superwall/gamepadValueChanged(gamepad:element:)`` from your own gamepad's valueChanged handler: ```swift controller.extendedGamepad?.valueChangedHandler = { gamepad, element in // Send values to Superwall - Superwall.gamepadValueChanged(gamepad: gamepad, element: element) + Superwall.shared.gamepadValueChanged(gamepad: gamepad, element: element) // ... rest of your code } diff --git a/Sources/SuperwallKit/Documentation.docc/InAppPreviews.md b/Sources/SuperwallKit/Documentation.docc/InAppPreviews.md index 120c6ad18..dcbf68fcf 100644 --- a/Sources/SuperwallKit/Documentation.docc/InAppPreviews.md +++ b/Sources/SuperwallKit/Documentation.docc/InAppPreviews.md @@ -19,7 +19,7 @@ Then, you'll need to handle the deep link within your app using ``Superwall/hand ```swift extension SuperwallService { static func handleDeepLink(_ url: URL) { - Superwall.handleDeepLink(url) + Superwall.shared.handleDeepLink(url) } } ``` diff --git a/Sources/SuperwallKit/Documentation.docc/SettingUserAttributes.md b/Sources/SuperwallKit/Documentation.docc/SettingUserAttributes.md index 15630b7e3..f086fc763 100644 --- a/Sources/SuperwallKit/Documentation.docc/SettingUserAttributes.md +++ b/Sources/SuperwallKit/Documentation.docc/SettingUserAttributes.md @@ -22,7 +22,7 @@ extension SuperwallService { "username": user.username, "profilePic": user.profilePicUrl ] - Superwall.setUserAttributes(attributes) + Superwall.shared.setUserAttributes(attributes) } } ``` diff --git a/Sources/SuperwallKit/Documentation.docc/ThirdPartyAnalytics.md b/Sources/SuperwallKit/Documentation.docc/ThirdPartyAnalytics.md index 7f82aa350..d7ec69660 100644 --- a/Sources/SuperwallKit/Documentation.docc/ThirdPartyAnalytics.md +++ b/Sources/SuperwallKit/Documentation.docc/ThirdPartyAnalytics.md @@ -79,6 +79,8 @@ extension SuperwallService: SuperwallDelegate { break case .paywallProductsLoadComplete(let triggeredEventName): break + case .paywallPresentationFail(reason: let reason): + break } } } diff --git a/Sources/SuperwallKit/Documentation.docc/TrackingEvents.md b/Sources/SuperwallKit/Documentation.docc/TrackingEvents.md index 21e311bdc..d1800c332 100644 --- a/Sources/SuperwallKit/Documentation.docc/TrackingEvents.md +++ b/Sources/SuperwallKit/Documentation.docc/TrackingEvents.md @@ -19,7 +19,7 @@ When you sign up for a Superwall account, we give you an example paywall and cam You use ``Superwall/track(event:params:paywallOverrides:paywallHandler:)`` to track events: ```swift -Superwall.track(event: "campaign_trigger") { paywallState in +Superwall.shared.track(event: "campaign_trigger") { paywallState in switch paywallState { case .presented(let paywallInfo): break @@ -51,7 +51,7 @@ final class Analytics { params: [String: Any] ) { // Superwall - Superwall.track( + Superwall.shared.track( event: event, params: params ) diff --git a/Sources/SuperwallKit/Game Controller/GameControllerManager.swift b/Sources/SuperwallKit/Game Controller/GameControllerManager.swift index 68e852307..9ab30032a 100644 --- a/Sources/SuperwallKit/Game Controller/GameControllerManager.swift +++ b/Sources/SuperwallKit/Game Controller/GameControllerManager.swift @@ -20,7 +20,7 @@ final class GameControllerManager: NSObject { weak var delegate: GameControllerDelegate? func setDelegate(_ delegate: GameControllerDelegate) { - guard Superwall.options.isGameControllerEnabled else { + guard Superwall.shared.options.isGameControllerEnabled else { return } self.delegate = delegate @@ -28,7 +28,7 @@ final class GameControllerManager: NSObject { func clearDelegate(_ delegate: PaywallViewController?) { guard - Superwall.options.isGameControllerEnabled, + Superwall.shared.options.isGameControllerEnabled, self.delegate == delegate else { return @@ -69,7 +69,7 @@ final class GameControllerManager: NSObject { gamepad: GCExtendedGamepad, element: GCControllerElement ) { - guard Superwall.options.isGameControllerEnabled else { + guard Superwall.shared.options.isGameControllerEnabled else { return } diff --git a/Sources/SuperwallKit/Game Controller/PublicGameController.swift b/Sources/SuperwallKit/Game Controller/PublicGameController.swift index 92c9aee61..01a942eb6 100644 --- a/Sources/SuperwallKit/Game Controller/PublicGameController.swift +++ b/Sources/SuperwallKit/Game Controller/PublicGameController.swift @@ -7,7 +7,7 @@ import GameController -public extension Superwall { +extension Superwall { /// Forwards Game controller events to the paywall. /// /// Call this in Gamepad's `valueChanged` function to forward game controller events to the paywall via `paywall.js` @@ -18,7 +18,7 @@ public extension Superwall { /// - gamepad: The extended Gamepad controller profile. /// - element: The game controller element. @MainActor - static func gamepadValueChanged( + public func gamepadValueChanged( gamepad: GCExtendedGamepad, element: GCControllerElement ) { diff --git a/Sources/SuperwallKit/Graveyard/SwiftUIGraveyard.swift b/Sources/SuperwallKit/Graveyard/SwiftUIGraveyard.swift index 518a03e0c..ea05c182c 100644 --- a/Sources/SuperwallKit/Graveyard/SwiftUIGraveyard.swift +++ b/Sources/SuperwallKit/Graveyard/SwiftUIGraveyard.swift @@ -8,7 +8,7 @@ import SwiftUI extension View { - @available(*, unavailable, message: "Please use the UIKit function Superwall.track(...) instead.") + @available(*, unavailable, message: "Please use the UIKit function Superwall.shared.track(...) instead.") public func triggerPaywall( forEvent event: String, withParams params: [String: Any]? = nil, diff --git a/Sources/SuperwallKit/Identity/IdentityInfo.swift b/Sources/SuperwallKit/Identity/IdentityInfo.swift new file mode 100644 index 000000000..8b19f6f0e --- /dev/null +++ b/Sources/SuperwallKit/Identity/IdentityInfo.swift @@ -0,0 +1,13 @@ +// +// File.swift +// +// +// Created by Yusuf Tör on 25/01/2023. +// + +import Foundation + +struct IdentityInfo { + let aliasId: String + let appUserId: String? +} diff --git a/Sources/SuperwallKit/Identity/IdentityManager.swift b/Sources/SuperwallKit/Identity/IdentityManager.swift index fb08cfde8..9a99fae08 100644 --- a/Sources/SuperwallKit/Identity/IdentityManager.swift +++ b/Sources/SuperwallKit/Identity/IdentityManager.swift @@ -124,7 +124,7 @@ class IdentityManager { throw LogoutError.notLoggedIn } - await Superwall.reset() + await Superwall.shared.reset() } /// Clears all stored user-specific variables. diff --git a/Sources/SuperwallKit/Identity/PublicIdentity.swift b/Sources/SuperwallKit/Identity/PublicIdentity.swift index 9730ff6c6..6ef36c854 100644 --- a/Sources/SuperwallKit/Identity/PublicIdentity.swift +++ b/Sources/SuperwallKit/Identity/PublicIdentity.swift @@ -16,18 +16,20 @@ public extension Superwall { /// The user will stay logged in until you call ``SuperwallKit/Superwall/logOut()``. If you call this while they're already logged in, it will throw an error of type ``IdentityError``. /// - Parameter userId: Your user's unique identifier, as defined by your backend system. /// - Throws: An error of type ``IdentityError``. - @objc static func logIn(userId: String) async throws { - try await shared.dependencyContainer.identityManager.logIn(userId: userId) + @objc func logIn(userId: String) async throws { + try await dependencyContainer.identityManager.logIn(userId: userId) } /// Logs in a user with their `userId` to retrieve paywalls that they've been assigned to. /// - /// This links a `userId` to Superwall's automatically generated alias. Call this as soon as you have a userId. If a user with a different id was previously identified, calling this will automatically call `Superwall.reset()` + /// This links a `userId` to Superwall's automatically generated alias. Call this as soon + /// as you have a userId. If a user with a different id was previously identified, calling this + /// will automatically call ``reset()`` /// - Parameters: /// - userId: Your user's unique identifier, as defined by your backend system. /// - completion: A completion block that accepts a `Result` enum. Its success value is /// the shared Superwall instance, and its failure error is of type ``IdentityError``. - static func logIn( + func logIn( userId: String, completion: ((Result) -> Void)? ) { @@ -46,34 +48,35 @@ public extension Superwall { } } + // MARK: - Create Account public extension Superwall { /// Creates an account with Superwall. This links a `userId` to Superwall's automatically generated alias. /// /// Call this as soon as you have a `userId`. If you are logging in an existing user, you should use - /// ``SuperwallKit/Superwall/logIn(userId:)`` instead, as that will retrieve their assigned paywalls. + /// ``Superwall/logIn(userId:)`` instead, as that will retrieve their assigned paywalls. /// /// - Parameter userId: Your user's unique identifier, as defined by your backend system. /// - Throws: An error of type ``IdentityError``. - @objc static func createAccount(userId: String) throws { - try shared.dependencyContainer.identityManager.createAccount(userId: userId) + @objc func createAccount(userId: String) throws { + try dependencyContainer.identityManager.createAccount(userId: userId) } } // MARK: - Log Out public extension Superwall { - /// Logs out the user. This calls ``SuperwallKit/Superwall/reset()``, which resets on-device paywall + /// Logs out the user. This calls ``reset()``, which resets on-device paywall /// assignments and the `userId` stored by Superwall. /// /// You must call this method before attempting to log in a new user. /// If a user isn't already logged in before calling this method, an error will be thrown. /// /// - Throws: An error of type ``LogoutError``. - @objc static func logOut() async throws { - try await shared.dependencyContainer.identityManager.logOut() + @objc func logOut() async throws { + try await dependencyContainer.identityManager.logOut() } - /// Logs out the user. This calls ``SuperwallKit/Superwall/reset()``, which resets on-device paywall + /// Logs out the user. This calls ``reset()``, which resets on-device paywall /// assignments and the `userId` stored by Superwall. /// /// You must call this method before attempting to log in a new user. @@ -82,7 +85,7 @@ public extension Superwall { /// - Parameters: /// - completion: A completion block that accepts a `Result` object. /// The `Result`'s success value is `Void` and failure error is of type ``LogoutError``. - static func logOut(completion: ((Result) -> Void)? = nil) { + func logOut(completion: ((Result) -> Void)? = nil) { Task { do { try await logOut() @@ -102,20 +105,20 @@ public extension Superwall { public extension Superwall { /// Resets the `userId`, on-device paywall assignments, and data stored /// by Superwall. - @objc static func reset() async { - shared.presentationItems.reset() - shared.dependencyContainer.identityManager.reset() - shared.dependencyContainer.storage.reset() - await shared.dependencyContainer.paywallManager.resetCache() - shared.dependencyContainer.configManager.reset() - shared.dependencyContainer.identityManager.didSetIdentity() + @objc func reset() async { + presentationItems.reset() + dependencyContainer.identityManager.reset() + dependencyContainer.storage.reset() + await dependencyContainer.paywallManager.resetCache() + dependencyContainer.configManager.reset() + dependencyContainer.identityManager.didSetIdentity() } /// Asynchronously resets the `userId` and data stored by Superwall. /// /// - Parameters: /// - completion: A completion block that is called when reset has completed. - static func reset(completion: (() -> Void)? = nil) { + func reset(completion: (() -> Void)? = nil) { Task { await reset() await MainActor.run { diff --git a/Sources/SuperwallKit/Identity/UserAttributes.swift b/Sources/SuperwallKit/Identity/UserAttributes.swift index e0005484c..4bd4da1ee 100644 --- a/Sources/SuperwallKit/Identity/UserAttributes.swift +++ b/Sources/SuperwallKit/Identity/UserAttributes.swift @@ -22,13 +22,13 @@ public extension Superwall { /// "username": user.username, /// "profilePic": user.profilePicUrl /// ] - /// Superwall.setUserAttributes(attributes) + /// Superwall.shared.setUserAttributes(attributes) /// ``` /// See for more. /// /// /// - Parameter custom: A `[String: Any?]` map used to describe any custom attributes you'd like to store to the user. Remember, keys begining with `$` are reserved for Superwall and will be dropped. Values can be any JSON encodable value, URLs or Dates. Arrays and dictionaries as values are not supported at this time, and will be dropped. - static func setUserAttributes(_ attributes: [String: Any?]) { + func setUserAttributes(_ attributes: [String: Any?]) { mergeAttributes(attributes) } @@ -47,7 +47,7 @@ public extension Superwall { /// [Superwall setUserAttributesDictionary: userAttributes]; /// ``` @available(swift, obsoleted: 1.0) - @objc static func setUserAttributesDictionary(_ attributes: NSDictionary) { + @objc func setUserAttributesDictionary(_ attributes: NSDictionary) { if let anyAttributes = attributes as? [String: Any] { mergeAttributes(anyAttributes) } else if let anyAttributes = attributes as? [String: Any?] { @@ -66,7 +66,7 @@ public extension Superwall { /// /// - Parameter keys: An array containing the keys you wish to remove from the user attributes dictionary. @available(swift, obsoleted: 1.0) - @objc static func removeUserAttributes(_ keys: [String]) { + @objc func removeUserAttributes(_ keys: [String]) { let userAttributes: [String: Any?] = keys.reduce([:]) { dictionary, key in var dictionary = dictionary dictionary[key] = nil @@ -75,7 +75,7 @@ public extension Superwall { setUserAttributes(userAttributes) } - private static func mergeAttributes(_ attributes: [String: Any?]) { + private func mergeAttributes(_ attributes: [String: Any?]) { Task { var customAttributes: [String: Any] = [:] @@ -94,7 +94,7 @@ public extension Superwall { ) let result = await track(trackableEvent) let eventParams = result.parameters.eventParams - shared.dependencyContainer.identityManager.mergeUserAttributes(eventParams) + dependencyContainer.identityManager.mergeUserAttributes(eventParams) } } } diff --git a/Sources/SuperwallKit/Logger/LogScope.swift b/Sources/SuperwallKit/Logger/LogScope.swift index 0118c6c5f..132e4efdb 100644 --- a/Sources/SuperwallKit/Logger/LogScope.swift +++ b/Sources/SuperwallKit/Logger/LogScope.swift @@ -8,7 +8,8 @@ import Foundation /// The possible scope of logs to print to the console. -public enum LogScope: String, Sendable { +@objc(SWKLogScope) +public enum LogScope: Int, Sendable, CustomStringConvertible { case localizationManager case bounceButton case coreData @@ -30,4 +31,51 @@ public enum LogScope: String, Sendable { case paywallViewController case cache case all + + public var description: String { + switch self { + case .localizationManager: + return "localizationManager" + case .bounceButton: + return "bounceButton" + case .coreData: + return "coreData" + case .configManager: + return "configManager" + case .debugManager: + return "debugManager" + case .debugViewController: + return "debugViewController" + case .localizationViewController: + return "localizationViewController" + case .gameControllerManager: + return "gameControllerManager" + case .device: + return "device" + case .network: + return "network" + case .paywallEvents: + return "paywallEvents" + case .productsManager: + return "productsManager" + case .storeKitManager: + return "storeKitManager" + case .events: + return "events" + case .receipts: + return "receipts" + case .superwallCore: + return "superwallCore" + case .paywallPresentation: + return "paywallPresentation" + case .paywallTransactions: + return "paywallTransactions" + case .paywallViewController: + return "paywallViewController" + case .cache: + return "cache" + case .all: + return "all" + } + } } diff --git a/Sources/SuperwallKit/Logger/Logger.swift b/Sources/SuperwallKit/Logger/Logger.swift index e3824f9c8..934ffe631 100644 --- a/Sources/SuperwallKit/Logger/Logger.swift +++ b/Sources/SuperwallKit/Logger/Logger.swift @@ -45,9 +45,10 @@ enum Logger: Loggable { logLevel: LogLevel, scope: LogScope ) -> Bool { - let exceedsCurrentLogLevel = logLevel.rawValue >= (Superwall.options.logging.level?.rawValue ?? 99) - let isInScope = Superwall.options.logging.scopes.contains(scope) - let allLogsActive = Superwall.options.logging.scopes.contains(.all) + let logging = Superwall.shared.options.logging + let exceedsCurrentLogLevel = logLevel.rawValue >= (logging.level?.rawValue ?? 99) + let isInScope = logging.scopes.contains(scope) + let allLogsActive = logging.scopes.contains(.all) return exceedsCurrentLogLevel && (isInScope || allLogsActive) @@ -78,9 +79,9 @@ enum Logger: Loggable { dumping["error"] = error } - await Superwall.shared.dependencyContainer.delegateAdapter?.handleLog( + await Superwall.shared.dependencyContainer.delegateAdapter.handleLog( level: logLevel.description, - scope: scope.rawValue, + scope: scope.description, message: message, info: info, error: error diff --git a/Sources/SuperwallKit/Misc/Constants.swift b/Sources/SuperwallKit/Misc/Constants.swift index dd91d0317..86baba0de 100644 --- a/Sources/SuperwallKit/Misc/Constants.swift +++ b/Sources/SuperwallKit/Misc/Constants.swift @@ -18,5 +18,5 @@ let sdkVersion = """ */ let sdkVersion = """ -3.0.0-beta.1 +3.0.0-beta.2 """ diff --git a/Sources/SuperwallKit/Models/Product/Product.swift b/Sources/SuperwallKit/Models/Product/Product.swift index fec4c3aae..785780905 100644 --- a/Sources/SuperwallKit/Models/Product/Product.swift +++ b/Sources/SuperwallKit/Models/Product/Product.swift @@ -8,7 +8,8 @@ import Foundation /// The type of product. -public enum ProductType: String, Codable, Sendable { +@objc(SWKProductType) +public enum ProductType: Int, Codable, Sendable { /// The primary product of the paywall. case primary @@ -17,18 +18,80 @@ public enum ProductType: String, Codable, Sendable { /// The tertiary product of the paywall. case tertiary + + enum InternalProductType: String, Codable { + case primary + case secondary + case tertiary + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + + switch self { + case .primary: + try container.encode(InternalProductType.primary.rawValue) + case .secondary: + try container.encode(InternalProductType.secondary.rawValue) + case .tertiary: + try container.encode(InternalProductType.tertiary.rawValue) + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let rawValue = try container.decode(String.self) + guard let internalProductType = InternalProductType(rawValue: rawValue) else { + throw DecodingError.typeMismatch( + InternalProductType.self, + .init( + codingPath: [], + debugDescription: "Didn't find a primary, secondary, or tertiary product type." + ) + ) + } + switch internalProductType { + case .primary: + self = .primary + case .secondary: + self = .secondary + case .tertiary: + self = .tertiary + } + } +} + +// MARK: - CustomStringConvertible +extension ProductType: CustomStringConvertible { + public var description: String { + switch self { + case .primary: + return InternalProductType.primary.rawValue + case .secondary: + return InternalProductType.secondary.rawValue + case .tertiary: + return InternalProductType.tertiary.rawValue + } + } } /// The product in the paywall. -public struct Product: Codable, Sendable { +@objcMembers +@objc(SWKProduct) +public final class Product: NSObject, Codable, Sendable { /// The type of product. - public var type: ProductType + public let type: ProductType /// The product identifier. - public var id: String + public let id: String enum CodingKeys: String, CodingKey { case type = "product" case id = "productId" } + + init(type: ProductType, id: String) { + self.type = type + self.id = id + } } diff --git a/Sources/SuperwallKit/Models/Triggers/Experiment.swift b/Sources/SuperwallKit/Models/Triggers/Experiment.swift index aae92de86..3a26b8ff3 100644 --- a/Sources/SuperwallKit/Models/Triggers/Experiment.swift +++ b/Sources/SuperwallKit/Models/Triggers/Experiment.swift @@ -17,18 +17,44 @@ import Foundation /// they are in a holdout group. /// /// To learn more, read . -public struct Experiment: Equatable, Hashable, Codable, Sendable { +@objc(SWKExperiment) +@objcMembers +public final class Experiment: NSObject, Codable, Sendable { public typealias ID = String - public struct Variant: Equatable, Hashable, Codable, Sendable { - public enum VariantType: String, Codable, Hashable, Sendable { - case treatment = "TREATMENT" - case holdout = "HOLDOUT" + @objc(SWKVariant) + public final class Variant: NSObject, Codable, Sendable { + @objc(SWKVariantType) + public enum VariantType: Int, Codable, Hashable, Sendable { + case treatment + case holdout + + enum InternalVariantType: String, Codable { + case treatment = "TREATMENT" + case holdout = "HOLDOUT" + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + + switch self { + case .treatment: + try container.encode(InternalVariantType.treatment.rawValue) + case .holdout: + try container.encode(InternalVariantType.holdout.rawValue) + } + } public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() - let rawValue = try container.decode(RawValue.self) - self = VariantType(rawValue: rawValue) ?? .treatment + let rawValue = try container.decode(String.self) + let internalVariantType = InternalVariantType(rawValue: rawValue) ?? .treatment + switch internalVariantType { + case .treatment: + self = .treatment + case .holdout: + self = .holdout + } } } @@ -40,6 +66,21 @@ public struct Experiment: Equatable, Hashable, Codable, Sendable { /// The identifier of the paywall variant. Only valid when the variant `type` is `treatment`. public let paywallId: String? + + init(id: String, type: VariantType, paywallId: String?) { + self.id = id + self.type = type + self.paywallId = paywallId + } + + public override func isEqual(_ object: Any?) -> Bool { + guard let object = object as? Variant else { + return false + } + return id == object.id + && type == object.type + && paywallId == object.paywallId + } } /// The id of the experiment. public let id: Experiment.ID @@ -101,6 +142,7 @@ public struct Experiment: Equatable, Hashable, Codable, Sendable { variant: Variant(id: "", type: .treatment, paywallId: id) ) } + } // MARK: - Stubbable diff --git a/Sources/SuperwallKit/Network/API.swift b/Sources/SuperwallKit/Network/API.swift index 83a4d5904..48cd97bf0 100644 --- a/Sources/SuperwallKit/Network/API.swift +++ b/Sources/SuperwallKit/Network/API.swift @@ -15,8 +15,7 @@ struct Api { static let version1 = "/api/v1/" static let scheme = "https" - init(configManager: ConfigManager) { - let networkEnvironment = configManager.options.networkEnvironment + init(networkEnvironment: SuperwallOptions.NetworkEnvironment) { self.base = Base(networkEnvironment: networkEnvironment) self.collector = Collector(networkEnvironment: networkEnvironment) self.hostDomain = networkEnvironment.hostDomain diff --git a/Sources/SuperwallKit/Network/Custom URL Session/CustomURLSession.swift b/Sources/SuperwallKit/Network/Custom URL Session/CustomURLSession.swift index c3bf08dc2..b501eaf6d 100644 --- a/Sources/SuperwallKit/Network/Custom URL Session/CustomURLSession.swift +++ b/Sources/SuperwallKit/Network/Custom URL Session/CustomURLSession.swift @@ -29,11 +29,8 @@ class CustomURLSession { } @discardableResult - func request( - _ endpoint: Endpoint, - isForDebugging: Bool = false - ) async throws -> Response { - guard let request = endpoint.makeRequest(forDebugging: isForDebugging) else { + func request(_ endpoint: Endpoint) async throws -> Response { + guard let request = endpoint.makeRequest() else { throw NetworkError.unknown } guard let auth = request.allHTTPHeaderFields?["Authorization"] else { diff --git a/Sources/SuperwallKit/Network/DeviceHelper.swift b/Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift similarity index 91% rename from Sources/SuperwallKit/Network/DeviceHelper.swift rename to Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift index 2955196e9..95c329d5e 100644 --- a/Sources/SuperwallKit/Network/DeviceHelper.swift +++ b/Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift @@ -3,6 +3,7 @@ // // Created by Jake Mor on 8/10/21. // +// swiftlint:disable type_body_length import UIKit import Foundation @@ -118,6 +119,22 @@ class DeviceHelper { #endif }() + /// The first URL scheme defined in the Info.plist. Assumes there's only one. + let urlScheme: String = { + guard let urlTypes = Bundle.main.infoDictionary?["CFBundleURLTypes"] as? [[String: Any]] else { + return "" + } + + var result = "" + if let urlTypeDictionary = urlTypes.first, + let urlSchemes = urlTypeDictionary["CFBundleURLSchemes"] as? [String], + let urlScheme = urlSchemes.first { + result = urlScheme + } + + return result + }() + private let appInstallDate: Date? = { guard let urlToDocumentsFolder = FileManager.default.urls( for: .documentDirectory, @@ -249,12 +266,13 @@ class DeviceHelper { } var templateDevice: DeviceTemplate { - let aliases = [identityManager.aliasId] + let identityInfo = factory.makeIdentityInfo() + let aliases = [identityInfo.aliasId] return DeviceTemplate( publicApiKey: storage.apiKey, platform: isMac ? "macOS" : "iOS", - appUserId: identityManager.appUserId ?? "", + appUserId: identityInfo.appUserId ?? "", aliases: aliases, vendorId: vendorId, appVersion: appVersion, @@ -287,23 +305,18 @@ class DeviceHelper { private unowned let storage: Storage private unowned let localizationManager: LocalizationManager - - // swiftlint:disable implicitly_unwrapped_optional - unowned var identityManager: IdentityManager! - // swiftlint:enable implicitly_unwrapped_optional + private unowned let factory: IdentityInfoFactory init( api: Api, storage: Storage, - localizationManager: LocalizationManager + localizationManager: LocalizationManager, + factory: IdentityInfoFactory ) { self.storage = storage self.localizationManager = localizationManager self.appInstalledAtString = appInstallDate?.isoString ?? "" + self.factory = factory reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, api.hostDomain) } - - func postInit(identityManager: IdentityManager) { - self.identityManager = identityManager - } } diff --git a/Sources/SuperwallKit/Network/Device Helper/DeviceInfo.swift b/Sources/SuperwallKit/Network/Device Helper/DeviceInfo.swift new file mode 100644 index 000000000..35cdc4c79 --- /dev/null +++ b/Sources/SuperwallKit/Network/Device Helper/DeviceInfo.swift @@ -0,0 +1,13 @@ +// +// File.swift +// +// +// Created by Yusuf Tör on 25/01/2023. +// + +import Foundation + +struct DeviceInfo { + let appInstalledAtString: String + let locale: String +} diff --git a/Sources/SuperwallKit/Network/Endpoint.swift b/Sources/SuperwallKit/Network/Endpoint.swift index 60aba7c16..101fd3a02 100644 --- a/Sources/SuperwallKit/Network/Endpoint.swift +++ b/Sources/SuperwallKit/Network/Endpoint.swift @@ -26,7 +26,7 @@ struct Endpoint { var requestId: String = UUID().uuidString let factory: ApiFactory - func makeRequest(forDebugging isForDebugging: Bool) -> URLRequest? { + func makeRequest() -> URLRequest? { let url: URL if let components = components { @@ -58,8 +58,7 @@ struct Endpoint { let headers = factory.makeHeaders( fromRequest: request, - requestId: requestId, - forDebugging: isForDebugging + requestId: requestId ) for header in headers { diff --git a/Sources/SuperwallKit/Network/Network.swift b/Sources/SuperwallKit/Network/Network.swift index a7c335c26..0188d5445 100644 --- a/Sources/SuperwallKit/Network/Network.swift +++ b/Sources/SuperwallKit/Network/Network.swift @@ -84,10 +84,7 @@ class Network { func getPaywalls() async throws -> [Paywall] { do { - let response = try await urlSession.request( - .paywalls(factory: factory), - isForDebugging: true - ) + let response = try await urlSession.request(.paywalls(factory: factory)) return response.paywalls } catch { Logger.debug( @@ -101,7 +98,6 @@ class Network { } func getConfig( - withRequestId requestId: String, injectedApplicationStatePublisher: (AnyPublisher)? = nil ) async throws -> Config { // Suspend until app is in foreground. @@ -114,6 +110,7 @@ class Network { .async() do { + let requestId = UUID().uuidString var config = try await urlSession.request(.config(requestId: requestId, factory: factory)) config.requestId = requestId return config diff --git a/Sources/SuperwallKit/Paywall View Controller/Loading/Listeners/PaddingListener.swift b/Sources/SuperwallKit/Paywall View Controller/Loading/Listeners/PaddingListener.swift deleted file mode 100644 index cea0b6bb0..000000000 --- a/Sources/SuperwallKit/Paywall View Controller/Loading/Listeners/PaddingListener.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// File.swift -// -// -// Created by Yusuf Tör on 12/10/2022. -// - -import SwiftUI - -struct PaddingListenerViewModifier: ViewModifier { - let movedUp: Published.Publisher - let model: LoadingModel - let maxPadding: CGFloat - - func body(content: Content) -> some View { - content - .onReceive(movedUp) { movedUp in - withAnimation { - if movedUp { - model.padding = maxPadding - } else { - model.padding = 0 - } - } - } - } -} - -extension View { - func listen( - to movedUp: Published.Publisher, - fromModel model: LoadingModel, - maxPadding: CGFloat - ) -> some View { - ModifiedContent( - content: self, - modifier: PaddingListenerViewModifier( - movedUp: movedUp, - model: model, - maxPadding: maxPadding - ) - ) - } -} diff --git a/Sources/SuperwallKit/Paywall View Controller/Loading/LoadingModel.swift b/Sources/SuperwallKit/Paywall View Controller/Loading/LoadingModel.swift deleted file mode 100644 index e2a711f9c..000000000 --- a/Sources/SuperwallKit/Paywall View Controller/Loading/LoadingModel.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// File.swift -// -// -// Created by Yusuf Tör on 11/10/2022. -// - -import SwiftUI - -final class LoadingModel: ObservableObject { - @Published var isAnimating = false - @Published var scaleAmount = 0.05 - @Published var rotationAmount: CGFloat = .pi - @Published var movedUp = false - @Published var padding: CGFloat = 0 - @Published var isHidden = false - private weak var delegate: LoadingDelegate? - - init(delegate: LoadingDelegate?) { - self.delegate = delegate - addObservers() - } - - private func addObservers() { - NotificationCenter.default.addObserver( - self, - selector: #selector(applicationWillResignActive), - name: UIApplication.willResignActiveNotification, - object: nil - ) - NotificationCenter.default.addObserver( - self, - selector: #selector(applicationDidBecomeActive), - name: UIApplication.didBecomeActiveNotification, - object: nil - ) - } - - @objc private func applicationWillResignActive() { - guard delegate?.loadingState == .loadingPurchase else { - return - } - movedUp = true - } - - @objc private func applicationDidBecomeActive() { - guard delegate?.loadingState == .loadingPurchase else { - return - } - movedUp = false - } -} diff --git a/Sources/SuperwallKit/Paywall Manager/PaywallCache.swift b/Sources/SuperwallKit/Paywall/Manager/PaywallCache.swift similarity index 70% rename from Sources/SuperwallKit/Paywall Manager/PaywallCache.swift rename to Sources/SuperwallKit/Paywall/Manager/PaywallCache.swift index 3ad44a402..20f7b646f 100644 --- a/Sources/SuperwallKit/Paywall Manager/PaywallCache.swift +++ b/Sources/SuperwallKit/Paywall/Manager/PaywallCache.swift @@ -15,9 +15,7 @@ final class PaywallCache: Sendable { } @MainActor - func getPaywallViewController( - withIdentifier identifier: String? - ) -> PaywallViewController? { + func getPaywallViewController(identifier: String?) -> PaywallViewController? { let key = PaywallCacheLogic.key( forIdentifier: identifier, locale: deviceLocaleString @@ -26,21 +24,19 @@ final class PaywallCache: Sendable { } @MainActor - func getPaywall(withKey key: String) -> PaywallViewController? { + func getPaywallViewController(key: String) -> PaywallViewController? { return PaywallViewController.cache.first { $0.cacheKey == key } } @MainActor - func removePaywall( - withIdentifier identifier: String? - ) { - if let viewController = getPaywallViewController(withIdentifier: identifier) { + func removePaywallViewController(identifier: String?) { + if let viewController = getPaywallViewController(identifier: identifier) { PaywallViewController.cache.remove(viewController) } } @MainActor - func removePaywall(withViewController viewController: PaywallViewController) { + func removePaywallViewController(_ viewController: PaywallViewController) { PaywallViewController.cache.remove(viewController) } diff --git a/Sources/SuperwallKit/Paywall Manager/PaywallCacheLogic.swift b/Sources/SuperwallKit/Paywall/Manager/PaywallCacheLogic.swift similarity index 100% rename from Sources/SuperwallKit/Paywall Manager/PaywallCacheLogic.swift rename to Sources/SuperwallKit/Paywall/Manager/PaywallCacheLogic.swift diff --git a/Sources/SuperwallKit/Paywall Manager/PaywallManager.swift b/Sources/SuperwallKit/Paywall/Manager/PaywallManager.swift similarity index 76% rename from Sources/SuperwallKit/Paywall Manager/PaywallManager.swift rename to Sources/SuperwallKit/Paywall/Manager/PaywallManager.swift index cb894faf4..02b9af1a8 100644 --- a/Sources/SuperwallKit/Paywall Manager/PaywallManager.swift +++ b/Sources/SuperwallKit/Paywall/Manager/PaywallManager.swift @@ -14,37 +14,26 @@ class PaywallManager { return PaywallViewController.cache.first { $0.isActive } } private unowned let paywallRequestManager: PaywallRequestManager + private unowned let factory: ViewControllerFactory & CacheFactory - // swiftlint:disable implicitly_unwrapped_optional - private var cache: PaywallCache! - // swiftlint:enable implicitly_unwrapped_optional + private lazy var cache: PaywallCache = factory.makeCache() - private let factory: ViewControllerFactory - - /// **NOTE**: Remember to call `postInit` after init. init( - factory: ViewControllerFactory, + factory: ViewControllerFactory & CacheFactory, paywallRequestManager: PaywallRequestManager ) { self.factory = factory self.paywallRequestManager = paywallRequestManager } - /// Initialises variables that can't be immediately init'd. - func postInit(deviceHelper: DeviceHelper) { - self.cache = PaywallCache(deviceLocaleString: deviceHelper.locale) - } - @MainActor - func removePaywall(withIdentifier identifier: String?) { - cache.removePaywall( - withIdentifier: identifier - ) + func removePaywall(identifier: String?) { + cache.removePaywallViewController(identifier: identifier) } @MainActor func removePaywallViewController(_ viewController: PaywallViewController) { - cache.removePaywall(withViewController: viewController) + cache.removePaywallViewController(viewController) } @MainActor @@ -64,12 +53,12 @@ class PaywallManager { @MainActor func getPaywallViewController( from request: PaywallRequest, - cached: Bool + cached: Bool = true ) async throws -> PaywallViewController { let paywall = try await paywallRequestManager.getPaywall(from: request) if cached, - let viewController = self.cache.getPaywallViewController(withIdentifier: paywall.identifier) { + let viewController = self.cache.getPaywallViewController(identifier: paywall.identifier) { // Set product-related vars again incase products have been substituted into paywall. viewController.paywall.products = paywall.products viewController.paywall.productIds = paywall.productIds diff --git a/Sources/SuperwallKit/Paywall Presentation/Get Track Result/GetTrackResult.swift b/Sources/SuperwallKit/Paywall/Presentation/Get Track Result/GetTrackResult.swift similarity index 82% rename from Sources/SuperwallKit/Paywall Presentation/Get Track Result/GetTrackResult.swift rename to Sources/SuperwallKit/Paywall/Presentation/Get Track Result/GetTrackResult.swift index 7b39ae863..2405aecf8 100644 --- a/Sources/SuperwallKit/Paywall Presentation/Get Track Result/GetTrackResult.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Get Track Result/GetTrackResult.swift @@ -14,38 +14,25 @@ enum GetTrackResultError: Error, Equatable { case paywallNotAvailable static func == (lhs: GetTrackResultError, rhs: GetTrackResultError) -> Bool { - switch lhs { - case .willNotPresent: - guard case .willNotPresent = rhs else { - return false - } + switch (lhs, rhs) { + case (.willNotPresent, .willNotPresent), + (.userIsSubscribed, .userIsSubscribed), + (.paywallNotAvailable, .paywallNotAvailable): return true - case .userIsSubscribed: - switch rhs { - case .userIsSubscribed: - return true - default: - return false - } - case .paywallNotAvailable: - switch rhs { - case .paywallNotAvailable: - return true - default: - return false - } + default: + return false } } } extension Superwall { - static func getTrackResult(for request: PresentationRequest) async -> TrackResult { + func getTrackResult(for request: PresentationRequest) async -> TrackResult { let presentationSubject = PresentationSubject(request) return await presentationSubject .eraseToAnyPublisher() .awaitIdentity() - .logPresentation("Called Superwall.getTrackResult") + .logPresentation("Called Superwall.shared.getTrackResult") .evaluateRules(isPreemptive: true) .checkForPaywallResult() .getPaywallViewControllerNoChecks() @@ -55,7 +42,6 @@ extension Superwall { } // MARK: - Async Publisher for GetTrackResult - extension Publisher where Output == TrackResult { /// Waits and returns the first value of the publisher. /// diff --git a/Sources/SuperwallKit/Paywall Presentation/Get Track Result/Operators/CheckForPaywallResultOperator.swift b/Sources/SuperwallKit/Paywall/Presentation/Get Track Result/Operators/CheckForPaywallResultOperator.swift similarity index 100% rename from Sources/SuperwallKit/Paywall Presentation/Get Track Result/Operators/CheckForPaywallResultOperator.swift rename to Sources/SuperwallKit/Paywall/Presentation/Get Track Result/Operators/CheckForPaywallResultOperator.swift diff --git a/Sources/SuperwallKit/Paywall Presentation/Get Track Result/Operators/CheckPaywallIsPresentableOperator.swift b/Sources/SuperwallKit/Paywall/Presentation/Get Track Result/Operators/CheckPaywallIsPresentableOperator.swift similarity index 92% rename from Sources/SuperwallKit/Paywall Presentation/Get Track Result/Operators/CheckPaywallIsPresentableOperator.swift rename to Sources/SuperwallKit/Paywall/Presentation/Get Track Result/Operators/CheckPaywallIsPresentableOperator.swift index 081aa0445..1cef3490a 100644 --- a/Sources/SuperwallKit/Paywall Presentation/Get Track Result/Operators/CheckPaywallIsPresentableOperator.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Get Track Result/Operators/CheckPaywallIsPresentableOperator.swift @@ -12,7 +12,7 @@ extension AnyPublisher where Output == PaywallVcPipelineOutput, Failure == Error func checkPaywallIsPresentable() -> AnyPublisher { asyncMap { input in if await InternalPresentationLogic.userSubscribedAndNotOverridden( - isUserSubscribed: input.request.injections.isUserSubscribed, + isUserSubscribed: input.request.flags.isUserSubscribed, overrides: .init( isDebuggerLaunched: false, shouldIgnoreSubscriptionStatus: input.request.paywallOverrides?.ignoreSubscriptionStatus, diff --git a/Sources/SuperwallKit/Paywall Presentation/Get Track Result/Operators/GetPaywallVcNoChecksOperator.swift b/Sources/SuperwallKit/Paywall/Presentation/Get Track Result/Operators/GetPaywallVcNoChecksOperator.swift similarity index 69% rename from Sources/SuperwallKit/Paywall Presentation/Get Track Result/Operators/GetPaywallVcNoChecksOperator.swift rename to Sources/SuperwallKit/Paywall/Presentation/Get Track Result/Operators/GetPaywallVcNoChecksOperator.swift index e651d62d7..cdcb24ad3 100644 --- a/Sources/SuperwallKit/Paywall Presentation/Get Track Result/Operators/GetPaywallVcNoChecksOperator.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Get Track Result/Operators/GetPaywallVcNoChecksOperator.swift @@ -16,27 +16,19 @@ extension AnyPublisher where Output == TriggerResultResponsePipelineOutput, Fail paywallId: input.experiment.variant.paywallId, experiment: input.experiment ) - let injections = input.request.injections - let paywallRequest = PaywallRequest( + let dependencyContainer = input.request.dependencyContainer + let paywallRequest = dependencyContainer.makePaywallRequest( eventData: input.request.presentationInfo.eventData, responseIdentifiers: responseIdentifiers, overrides: .init( products: input.request.paywallOverrides?.products, isFreeTrial: input.request.presentationInfo.freeTrialOverride - ), - injections: .init( - sessionEventsManager: injections.sessionEventsManager, - storeKitManager: injections.storeKitManager, - configManager: injections.configManager, - network: injections.network, - debugManager: injections.debugManager ) ) do { - let paywallViewController = try await input.request.injections.paywallManager.getPaywallViewController( - from: paywallRequest, - cached: input.request.cached + let paywallViewController = try await dependencyContainer.paywallManager.getPaywallViewController( + from: paywallRequest ) let output = PaywallVcPipelineOutput( diff --git a/Sources/SuperwallKit/Paywall Presentation/Get Track Result/TrackInfoObjc.swift b/Sources/SuperwallKit/Paywall/Presentation/Get Track Result/TrackInfoObjc.swift similarity index 74% rename from Sources/SuperwallKit/Paywall Presentation/Get Track Result/TrackInfoObjc.swift rename to Sources/SuperwallKit/Paywall/Presentation/Get Track Result/TrackInfoObjc.swift index 97466c066..4f65ba0ce 100644 --- a/Sources/SuperwallKit/Paywall Presentation/Get Track Result/TrackInfoObjc.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Get Track Result/TrackInfoObjc.swift @@ -12,10 +12,22 @@ import Foundation /// Contains the possible cases resulting from tracking an event. @objc(SWKTrackResult) public enum TrackResultObjc: Int, Sendable, Equatable { + /// This event was not found on the dashboard. + /// + /// Please make sure you have added the event to a campaign on the dashboard and + /// double check its spelling. case eventNotFound + + /// No matching rule was found for this trigger so no paywall will be shown. case noRuleMatch + + /// A matching rule was found and this user will be shown a paywall. case paywall + + /// A matching rule was found and this user was assigned to a holdout group so will not be shown a paywall. case holdout + + /// An error occurred and the user will not be shown a paywall. case error } diff --git a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/InternalPresentation.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/InternalPresentation.swift similarity index 95% rename from Sources/SuperwallKit/Paywall Presentation/Internal Presentation/InternalPresentation.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/InternalPresentation.swift index 577f4ea8e..0a559ad84 100644 --- a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/InternalPresentation.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/InternalPresentation.swift @@ -32,7 +32,7 @@ extension Superwall { presentationSubject .eraseToAnyPublisher() .awaitIdentity() - .logPresentation("Called Superwall.track") + .logPresentation("Called Superwall.shared.track") .checkDebuggerPresentation(paywallStatePublisher) .evaluateRules() .checkUserSubscription(paywallStatePublisher) @@ -62,7 +62,7 @@ extension Superwall { // Remove the currently presenting paywall from cache. await MainActor.run { if let presentingPaywallIdentifier = Superwall.shared.paywallViewController?.paywall.identifier { - dependencyContainer.paywallManager.removePaywall(withIdentifier: presentingPaywallIdentifier) + dependencyContainer.paywallManager.removePaywall(identifier: presentingPaywallIdentifier) } } diff --git a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/InternalPresentationLogic.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/InternalPresentationLogic.swift similarity index 100% rename from Sources/SuperwallKit/Paywall Presentation/Internal Presentation/InternalPresentationLogic.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/InternalPresentationLogic.swift diff --git a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/AwaitIdentityOperator.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/AwaitIdentityOperator.swift similarity index 89% rename from Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/AwaitIdentityOperator.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/AwaitIdentityOperator.swift index ed0f2bfae..b0a1f17ea 100644 --- a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/AwaitIdentityOperator.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/AwaitIdentityOperator.swift @@ -14,7 +14,7 @@ extension AnyPublisher where Output == PresentationRequest, Failure == Error { func awaitIdentity() -> AnyPublisher { subscribe(on: DispatchQueue.global(qos: .userInitiated)) .flatMap { request in - zip(request.injections.identityManager.hasIdentity) + zip(request.dependencyContainer.identityManager.hasIdentity) } .map { request, _ in return request diff --git a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/CheckDebuggerPresentationOperator.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/CheckDebuggerPresentationOperator.swift similarity index 92% rename from Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/CheckDebuggerPresentationOperator.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/CheckDebuggerPresentationOperator.swift index 540a53753..7f327758e 100644 --- a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/CheckDebuggerPresentationOperator.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/CheckDebuggerPresentationOperator.swift @@ -15,7 +15,7 @@ extension AnyPublisher where Output == (PresentationRequest, DebugInfo), Failure _ paywallStatePublisher: PassthroughSubject ) -> AnyPublisher { tryMap { request, debugInfo in - if request.injections.isDebuggerLaunched { + if request.flags.isDebuggerLaunched { guard request.presentingViewController is DebugViewController else { let error = InternalPresentationLogic.presentationError( domain: "SWPresentationError", @@ -25,7 +25,7 @@ extension AnyPublisher where Output == (PresentationRequest, DebugInfo), Failure ) Task.detached(priority: .utility) { let trackedEvent = InternalSuperwallEvent.UnableToPresent(state: .debuggerLaunched) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) } let state: PaywallState = .skipped(.error(error)) paywallStatePublisher.send(state) diff --git a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/CheckPaywallPresentableOperator.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/CheckPaywallPresentableOperator.swift similarity index 88% rename from Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/CheckPaywallPresentableOperator.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/CheckPaywallPresentableOperator.swift index 9ed62a691..c49bb2d61 100644 --- a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/CheckPaywallPresentableOperator.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/CheckPaywallPresentableOperator.swift @@ -30,9 +30,9 @@ extension AnyPublisher where Output == PaywallVcPipelineOutput, Failure == Error ) -> AnyPublisher { asyncMap { input in if await InternalPresentationLogic.userSubscribedAndNotOverridden( - isUserSubscribed: input.request.injections.isUserSubscribed, + isUserSubscribed: input.request.flags.isUserSubscribed, overrides: .init( - isDebuggerLaunched: input.request.injections.isDebuggerLaunched, + isDebuggerLaunched: input.request.flags.isDebuggerLaunched, shouldIgnoreSubscriptionStatus: input.request.paywallOverrides?.ignoreSubscriptionStatus, presentationCondition: input.paywallViewController.paywall.presentation.condition ) @@ -41,7 +41,7 @@ extension AnyPublisher where Output == PaywallVcPipelineOutput, Failure == Error let trackedEvent = InternalSuperwallEvent.UnableToPresent( state: .userIsSubscribed ) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) } let state: PaywallState = .skipped(.userIsSubscribed) paywallStatePublisher.send(state) @@ -50,12 +50,12 @@ extension AnyPublisher where Output == PaywallVcPipelineOutput, Failure == Error } if input.request.presentingViewController == nil { - await input.request.injections.superwall.createPresentingWindowIfNeeded() + await Superwall.shared.createPresentingWindowIfNeeded() } // Make sure there's a presenter. If there isn't throw an error if no paywall is being presented let providedViewController = input.request.presentingViewController - let rootViewController = await input.request.injections.superwall.presentationItems.window?.rootViewController + let rootViewController = await Superwall.shared.presentationItems.window?.rootViewController guard let presenter = (providedViewController ?? rootViewController) else { Logger.debug( @@ -74,7 +74,7 @@ extension AnyPublisher where Output == PaywallVcPipelineOutput, Failure == Error ) Task.detached(priority: .utility) { let trackedEvent = InternalSuperwallEvent.UnableToPresent(state: .noPresenter) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) } let state: PaywallState = .skipped(.error(error)) paywallStatePublisher.send(state) @@ -82,7 +82,7 @@ extension AnyPublisher where Output == PaywallVcPipelineOutput, Failure == Error throw PresentationPipelineError.cancelled } - let sessionEventsManager = input.request.injections.sessionEventsManager + let sessionEventsManager = input.request.dependencyContainer.sessionEventsManager await sessionEventsManager.triggerSession.activateSession( for: input.request.presentationInfo, on: input.request.presentingViewController, diff --git a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/CheckUserSubscriptionOperator.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/CheckUserSubscriptionOperator.swift similarity index 91% rename from Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/CheckUserSubscriptionOperator.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/CheckUserSubscriptionOperator.swift index 1c0d48f54..5e86f097f 100644 --- a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/CheckUserSubscriptionOperator.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/CheckUserSubscriptionOperator.swift @@ -19,10 +19,10 @@ extension AnyPublisher where Output == AssignmentPipelineOutput, Failure == Erro case .paywall: return input default: - if input.request.injections.isUserSubscribed { + if input.request.flags.isUserSubscribed { Task.detached(priority: .utility) { let trackedEvent = InternalSuperwallEvent.UnableToPresent(state: .userIsSubscribed) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) } paywallStatePublisher.send(.skipped(.userIsSubscribed)) paywallStatePublisher.send(completion: .finished) diff --git a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/ConfirmHoldoutAssignmentOperator.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/ConfirmHoldoutAssignmentOperator.swift similarity index 87% rename from Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/ConfirmHoldoutAssignmentOperator.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/ConfirmHoldoutAssignmentOperator.swift index fb3d69a8f..9493bc509 100644 --- a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/ConfirmHoldoutAssignmentOperator.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/ConfirmHoldoutAssignmentOperator.swift @@ -18,7 +18,7 @@ extension AnyPublisher where Output == AssignmentPipelineOutput, Failure == Erro switch input.triggerResult { case .holdout: if let confirmableAssignment = input.confirmableAssignment { - input.request.injections.configManager.confirmAssignment(confirmableAssignment) + input.request.dependencyContainer.configManager.confirmAssignment(confirmableAssignment) } default: break diff --git a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/ConfirmPaywallAssignmentOperator.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/ConfirmPaywallAssignmentOperator.swift similarity index 83% rename from Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/ConfirmPaywallAssignmentOperator.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/ConfirmPaywallAssignmentOperator.swift index 0940b621f..14497bec8 100644 --- a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/ConfirmPaywallAssignmentOperator.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/ConfirmPaywallAssignmentOperator.swift @@ -17,12 +17,12 @@ extension AnyPublisher where Output == PresentablePipelineOutput, Failure == Err func confirmPaywallAssignment() -> AnyPublisher { map { input in // Debuggers shouldn't confirm assignments. - if input.request.injections.isDebuggerLaunched { + if input.request.flags.isDebuggerLaunched { return input } if let confirmableAssignment = input.confirmableAssignment { - input.request.injections.configManager.confirmAssignment(confirmableAssignment) + input.request.dependencyContainer.configManager.confirmAssignment(confirmableAssignment) } return input } diff --git a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/EvaluateRulesOperator.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/EvaluateRulesOperator.swift similarity index 85% rename from Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/EvaluateRulesOperator.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/EvaluateRulesOperator.swift index afafa278d..9b6abf7c2 100644 --- a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/EvaluateRulesOperator.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/EvaluateRulesOperator.swift @@ -26,15 +26,14 @@ extension AnyPublisher where Output == (PresentationRequest, DebugInfo), Failure func evaluateRules(isPreemptive: Bool = false) -> AnyPublisher { tryMap { request, debugInfo in if let eventData = request.presentationInfo.eventData { - let assignmentLogic = AssignmentLogic( - configManager: request.injections.configManager, - storage: request.injections.storage, - identityManager: request.injections.identityManager, - deviceHelper: request.injections.deviceHelper + let assignmentLogic = RuleLogic( + configManager: request.dependencyContainer.configManager, + storage: request.dependencyContainer.storage, + factory: request.dependencyContainer ) let eventOutcome = assignmentLogic.evaluateRules( forEvent: eventData, - triggers: request.injections.configManager.triggersByEventName, + triggers: request.dependencyContainer.configManager.triggersByEventName, isPreemptive: isPreemptive ) let confirmableAssignment = eventOutcome.confirmableAssignment diff --git a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/GetPaywallVcOperator.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/GetPaywallVcOperator.swift similarity index 80% rename from Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/GetPaywallVcOperator.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/GetPaywallVcOperator.swift index 8499f9103..fdedd7877 100644 --- a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/GetPaywallVcOperator.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/GetPaywallVcOperator.swift @@ -34,32 +34,25 @@ extension AnyPublisher where Output == TriggerResultResponsePipelineOutput, Fail paywallId: input.experiment.variant.paywallId, experiment: input.experiment ) - let injections = input.request.injections - let paywallRequest = PaywallRequest( + let dependencyContainer = input.request.dependencyContainer + + let paywallRequest = dependencyContainer.makePaywallRequest( eventData: input.request.presentationInfo.eventData, responseIdentifiers: responseIdentifiers, overrides: .init( products: input.request.paywallOverrides?.products, isFreeTrial: input.request.presentationInfo.freeTrialOverride - ), - injections: .init( - sessionEventsManager: injections.sessionEventsManager, - storeKitManager: injections.storeKitManager, - configManager: injections.configManager, - network: injections.network, - debugManager: injections.debugManager ) ) do { - let paywallManager = input.request.injections.paywallManager - let paywallViewController = try await paywallManager.getPaywallViewController( + let paywallViewController = try await dependencyContainer.paywallManager.getPaywallViewController( from: paywallRequest, - cached: input.request.cached && !input.request.injections.isDebuggerLaunched + cached: !input.request.flags.isDebuggerLaunched ) // if there's a paywall being presented, don't do anything - if input.request.injections.isPaywallPresented { + if input.request.flags.isPaywallPresented { Logger.debug( logLevel: .error, scope: .paywallPresentation, @@ -74,7 +67,7 @@ extension AnyPublisher where Output == TriggerResultResponsePipelineOutput, Fail ) Task.detached(priority: .utility) { let trackedEvent = InternalSuperwallEvent.UnableToPresent(state: .alreadyPresented) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) } let state: PaywallState = .skipped(.error(error)) paywallStatePublisher.send(state) @@ -92,15 +85,15 @@ extension AnyPublisher where Output == TriggerResultResponsePipelineOutput, Fail return output } catch { if InternalPresentationLogic.userSubscribedAndNotOverridden( - isUserSubscribed: input.request.injections.isUserSubscribed, + isUserSubscribed: input.request.flags.isUserSubscribed, overrides: .init( - isDebuggerLaunched: input.request.injections.isDebuggerLaunched, + isDebuggerLaunched: input.request.flags.isDebuggerLaunched, shouldIgnoreSubscriptionStatus: input.request.paywallOverrides?.ignoreSubscriptionStatus ) ) { Task.detached(priority: .utility) { let trackedEvent = InternalSuperwallEvent.UnableToPresent(state: .userIsSubscribed) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) } let state: PaywallState = .skipped(.userIsSubscribed) paywallStatePublisher.send(state) @@ -117,7 +110,7 @@ extension AnyPublisher where Output == TriggerResultResponsePipelineOutput, Fail ) Task.detached(priority: .utility) { let trackedEvent = InternalSuperwallEvent.UnableToPresent(state: .noPaywallViewController) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) } paywallStatePublisher.send(.skipped(.error(error))) paywallStatePublisher.send(completion: .finished) diff --git a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/HandleTriggerResultOperator.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/HandleTriggerResultOperator.swift similarity index 88% rename from Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/HandleTriggerResultOperator.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/HandleTriggerResultOperator.swift index 4f6c5b677..c4f32afa0 100644 --- a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/HandleTriggerResultOperator.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/HandleTriggerResultOperator.swift @@ -39,7 +39,7 @@ extension AnyPublisher where Output == AssignmentPipelineOutput, Failure == Erro experiment: experiment ) case .holdout(let experiment): - let sessionEventsManager = input.request.injections.sessionEventsManager + let sessionEventsManager = input.request.dependencyContainer.sessionEventsManager await sessionEventsManager.triggerSession.activateSession( for: input.request.presentationInfo, on: input.request.presentingViewController, @@ -49,11 +49,11 @@ extension AnyPublisher where Output == AssignmentPipelineOutput, Failure == Erro let trackedEvent = InternalSuperwallEvent.UnableToPresent( state: .holdout(experiment) ) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) } paywallStatePublisher.send(.skipped(.holdout(experiment))) case .noRuleMatch: - let sessionEventsManager = input.request.injections.sessionEventsManager + let sessionEventsManager = input.request.dependencyContainer.sessionEventsManager await sessionEventsManager.triggerSession.activateSession( for: input.request.presentationInfo, on: input.request.presentingViewController, @@ -61,13 +61,13 @@ extension AnyPublisher where Output == AssignmentPipelineOutput, Failure == Erro ) Task.detached(priority: .utility) { let trackedEvent = InternalSuperwallEvent.UnableToPresent(state: .noRuleMatch) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) } paywallStatePublisher.send(.skipped(.noRuleMatch)) case .eventNotFound: Task.detached(priority: .utility) { let trackedEvent = InternalSuperwallEvent.UnableToPresent(state: .eventNotFound) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) } paywallStatePublisher.send(.skipped(.eventNotFound)) case let .error(error): @@ -80,7 +80,7 @@ extension AnyPublisher where Output == AssignmentPipelineOutput, Failure == Erro ) Task.detached(priority: .utility) { let trackedEvent = InternalSuperwallEvent.UnableToPresent(state: .noPaywallViewController) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) } paywallStatePublisher.send(.skipped(.error(error))) } diff --git a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/LogPresentationOperator.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/LogPresentationOperator.swift similarity index 87% rename from Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/LogPresentationOperator.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/LogPresentationOperator.swift index 479a5b632..f68bede36 100644 --- a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/LogPresentationOperator.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/LogPresentationOperator.swift @@ -20,11 +20,10 @@ extension AnyPublisher where Output == PresentationRequest, Failure == Error { let eventData = request.presentationInfo.eventData let debugInfo: [String: Any] = [ "on": request.presentingViewController.debugDescription, - "fromEvent": eventData.debugDescription as Any, - "cached": request.cached + "fromEvent": eventData.debugDescription as Any ] - request.injections.logger.debug( + Logger.debug( logLevel: .debug, scope: .paywallPresentation, message: message, diff --git a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/PresentPaywallOperator.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/PresentPaywallOperator.swift similarity index 95% rename from Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/PresentPaywallOperator.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/PresentPaywallOperator.swift index 7cd43889b..1df185aa6 100644 --- a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/PresentPaywallOperator.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/PresentPaywallOperator.swift @@ -34,7 +34,7 @@ extension AnyPublisher where Output == PresentablePipelineOutput, Failure == Err paywallStatePublisher.send(state) promise(.success(input)) } else { - input.request.injections.logger.debug( + Logger.debug( logLevel: .info, scope: .paywallPresentation, message: "Paywall Already Presented", @@ -48,7 +48,7 @@ extension AnyPublisher where Output == PresentablePipelineOutput, Failure == Err ) Task.detached(priority: .utility) { let trackedEvent = InternalSuperwallEvent.UnableToPresent(state: .alreadyPresented) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) } paywallStatePublisher.send(.skipped(.error(error))) paywallStatePublisher.send(completion: .finished) diff --git a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/PresentationPipelineError.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/PresentationPipelineError.swift similarity index 100% rename from Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/PresentationPipelineError.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/PresentationPipelineError.swift diff --git a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/StorePresentationObjectsOperator.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/StorePresentationObjectsOperator.swift similarity index 92% rename from Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/StorePresentationObjectsOperator.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/StorePresentationObjectsOperator.swift index ccf21c818..37e3df04d 100644 --- a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Operators/StorePresentationObjectsOperator.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Operators/StorePresentationObjectsOperator.swift @@ -25,7 +25,7 @@ extension AnyPublisher where Output == PresentablePipelineOutput, Failure == Err request: input.request, statePublisher: paywallStatePublisher ) - input.request.injections.superwall.presentationItems.last = lastPaywallPresentation + Superwall.shared.presentationItems.last = lastPaywallPresentation presentationSubject.send(completion: .finished) return input } diff --git a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/PaywallPresentationFailureReason.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/PaywallPresentationFailureReason.swift similarity index 100% rename from Sources/SuperwallKit/Paywall Presentation/Internal Presentation/PaywallPresentationFailureReason.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/PaywallPresentationFailureReason.swift diff --git a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Presentation Request/PaywallOverrides.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation Request/PaywallOverrides.swift similarity index 100% rename from Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Presentation Request/PaywallOverrides.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation Request/PaywallOverrides.swift diff --git a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Presentation Request/PresentationInfo.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation Request/PresentationInfo.swift similarity index 100% rename from Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Presentation Request/PresentationInfo.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation Request/PresentationInfo.swift diff --git a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Presentation Request/PresentationRequest.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation Request/PresentationRequest.swift similarity index 63% rename from Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Presentation Request/PresentationRequest.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation Request/PresentationRequest.swift index 3f49ad64d..a7baa6397 100644 --- a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Presentation Request/PresentationRequest.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation Request/PresentationRequest.swift @@ -16,29 +16,17 @@ struct PresentationRequest { /// The view controller to present the paywall on, if any. var presentingViewController: UIViewController? - /// Defines whether to retrieve the cached paywall. Defaults to `true`. - var cached = true - /// Overrides the default behavior and products of a paywall. var paywallOverrides: PaywallOverrides? - struct Injections { - unowned var configManager: ConfigManager - unowned let storage: Storage - unowned let sessionEventsManager: SessionEventsManager - unowned var paywallManager: PaywallManager - let superwall: Superwall = .shared - var logger: Loggable.Type = Logger.self - unowned let storeKitManager: StoreKitManager - unowned let network: Network - unowned let debugManager: DebugManager - unowned var identityManager: IdentityManager - unowned let deviceHelper: DeviceHelper + struct Flags { var isDebuggerLaunched: Bool var isUserSubscribed: Bool var isPaywallPresented: Bool } - var injections: Injections + var flags: Flags + + unowned let dependencyContainer: DependencyContainer /// A `Just` publisher that that emits the request object once and finishes. var publisher: AnyPublisher { @@ -50,7 +38,7 @@ struct PresentationRequest { extension PresentationRequest: Stubbable { static func stub() -> PresentationRequest { - let dependencyContainer = DependencyContainer(apiKey: "abc") + let dependencyContainer = DependencyContainer() return dependencyContainer.makePresentationRequest( .explicitTrigger(.stub()), paywallOverrides: nil, diff --git a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Presentation State/PaywallDismissedResult.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation State/PaywallDismissedResult.swift similarity index 100% rename from Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Presentation State/PaywallDismissedResult.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation State/PaywallDismissedResult.swift diff --git a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Presentation State/PaywallSkippedReason.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation State/PaywallSkippedReason.swift similarity index 100% rename from Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Presentation State/PaywallSkippedReason.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation State/PaywallSkippedReason.swift diff --git a/Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Presentation State/PaywallState.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation State/PaywallState.swift similarity index 100% rename from Sources/SuperwallKit/Paywall Presentation/Internal Presentation/Presentation State/PaywallState.swift rename to Sources/SuperwallKit/Paywall/Presentation/Internal Presentation/Presentation State/PaywallState.swift diff --git a/Sources/SuperwallKit/Paywall Presentation/PaywallInfo.swift b/Sources/SuperwallKit/Paywall/Presentation/PaywallInfo.swift similarity index 99% rename from Sources/SuperwallKit/Paywall Presentation/PaywallInfo.swift rename to Sources/SuperwallKit/Paywall/Presentation/PaywallInfo.swift index cd26893cd..15aa6152c 100644 --- a/Sources/SuperwallKit/Paywall Presentation/PaywallInfo.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/PaywallInfo.swift @@ -255,7 +255,7 @@ public final class PaywallInfo: NSObject { // MARK: - Stubbable extension PaywallInfo: Stubbable { static func stub() -> PaywallInfo { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() return PaywallInfo( databaseId: "abc", identifier: "1", diff --git a/Sources/SuperwallKit/Paywall Presentation/PresentationItems.swift b/Sources/SuperwallKit/Paywall/Presentation/PresentationItems.swift similarity index 100% rename from Sources/SuperwallKit/Paywall Presentation/PresentationItems.swift rename to Sources/SuperwallKit/Paywall/Presentation/PresentationItems.swift diff --git a/Sources/SuperwallKit/Paywall Presentation/PublicPresentation.swift b/Sources/SuperwallKit/Paywall/Presentation/PublicPresentation.swift similarity index 93% rename from Sources/SuperwallKit/Paywall Presentation/PublicPresentation.swift rename to Sources/SuperwallKit/Paywall/Presentation/PublicPresentation.swift index 8e5467f18..442625e0c 100644 --- a/Sources/SuperwallKit/Paywall Presentation/PublicPresentation.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/PublicPresentation.swift @@ -19,11 +19,11 @@ public extension Superwall { /// - Parameters: /// - completion: An optional completion block that gets called after the paywall is dismissed. Defaults to nil. @MainActor - static func dismiss(_ completion: (() -> Void)? = nil) { - guard let paywallViewController = shared.paywallViewController else { + func dismiss(_ completion: (() -> Void)? = nil) { + guard let paywallViewController = paywallViewController else { return } - shared.dismiss( + dismiss( paywallViewController, state: .closed, completion: completion @@ -32,12 +32,12 @@ public extension Superwall { /// Dismisses the presented paywall. @MainActor - @objc static func dismiss() async { - guard let paywallViewController = shared.paywallViewController else { + @objc func dismiss() async { + guard let paywallViewController = paywallViewController else { return } return await withCheckedContinuation { continuation in - shared.dismiss( + dismiss( paywallViewController, state: .closed ) { @@ -66,7 +66,7 @@ public extension Superwall { /// - onDismiss: A completion block that gets called when the paywall is dismissed by the user, by way of purchasing, restoring or manually dismissing. Defaults to `nil`. Accepts a `Bool` that is `true` if the user purchased a product and `false` if not, a `String?` equal to the product id of the purchased product (if any) and a ``PaywallInfo`` object containing information about the paywall. /// - onSkip: A completion block that gets called when the paywall's presentation is skipped. Defaults to `nil`. Accepts a ``PaywallSkippedReasonObjc`` object and an `NSError` with more details. @available(swift, obsoleted: 1.0) - @objc static func track( + @objc func track( event: String, params: [String: Any]? = nil, products: PaywallProducts? = nil, @@ -86,16 +86,16 @@ public extension Superwall { event: event, params: params, paywallOverrides: overrides - ) { state in + ) { [weak self] state in switch state { case .presented(let paywallInfo): onPresent?(paywallInfo) case .dismissed(let result): if let onDismiss = onDismiss { - onDismissConverter(result, completion: onDismiss) + self?.onDismissConverter(result, completion: onDismiss) } case .skipped(let reason): - onSkipConverter(reason: reason, completion: onSkip) + self?.onSkipConverter(reason: reason, completion: onSkip) } } } @@ -115,7 +115,7 @@ public extension Superwall { /// - params: Optional parameters you'd like to pass with your event. These can be referenced within the rules of your campaign. Keys beginning with `$` are reserved for Superwall and will be dropped. Values can be any JSON encodable value, URLs or Dates. Arrays and dictionaries as values are not supported at this time, and will be dropped. /// - paywallOverrides: An optional ``PaywallOverrides`` object whose parameters override the paywall defaults. Use this to override products, presentation style, and whether it ignores the subscription status. Defaults to `nil`. /// - paywallHandler: An optional callback that provides updates on the state of the paywall via a ``PaywallState`` object. - static func track( + func track( event: String, params: [String: Any]? = nil, paywallOverrides: PaywallOverrides? = nil, @@ -150,7 +150,7 @@ public extension Superwall { /// - paywallOverrides: An optional ``PaywallOverrides`` object whose parameters override the paywall defaults. Use this to override products, presentation style, and whether it ignores the subscription status. Defaults to `nil`. /// /// - Returns: A publisher that provides updates on the state of the paywall via a ``PaywallState`` object. - static func publisher( + func publisher( forEvent event: String, params: [String: Any]? = nil, paywallOverrides: PaywallOverrides? = nil @@ -161,17 +161,17 @@ public extension Superwall { canImplicitlyTriggerPaywall: false, customParameters: params ?? [:] ) - let trackResult = await track(trackableEvent) - let isPaywallPresented = await shared.isPaywallPresented + let trackResult = await self.track(trackableEvent) + let isPaywallPresented = await self.isPaywallPresented return (trackResult, isPaywallPresented) } .flatMap { trackResult, isPaywallPresented in - let presentationRequest = shared.dependencyContainer.makePresentationRequest( + let presentationRequest = self.dependencyContainer.makePresentationRequest( .explicitTrigger(trackResult.data), paywallOverrides: paywallOverrides, isPaywallPresented: isPaywallPresented ) - return shared.internallyPresent(presentationRequest) + return self.internallyPresent(presentationRequest) } .eraseToAnyPublisher() } @@ -192,7 +192,7 @@ public extension Superwall { /// - params: Optional parameters you'd like to pass with your event. /// /// - Returns: A ``TrackResult`` that indicates the result of tracking an event. - static func getTrackResult( + func getTrackResult( forEvent event: String, params: [String: Any]? = nil ) async -> TrackResult { @@ -207,7 +207,7 @@ public extension Superwall { let parameters = await TrackingLogic.processParameters( fromTrackableEvent: trackableEvent, eventCreatedAt: eventCreatedAt, - appSessionId: shared.dependencyContainer.appSessionManager.appSession.id + appSessionId: dependencyContainer.appSessionManager.appSession.id ) let eventData = EventData( @@ -216,7 +216,7 @@ public extension Superwall { createdAt: eventCreatedAt ) - let presentationRequest = shared.dependencyContainer.makePresentationRequest( + let presentationRequest = dependencyContainer.makePresentationRequest( .explicitTrigger(eventData), isDebuggerLaunched: false, isPaywallPresented: false @@ -241,7 +241,7 @@ public extension Superwall { /// - params: Optional parameters you'd like to pass with your event. /// - completion: A completion block that accepts a ``TrackResult`` indicating /// the result of tracking an event. - static func getTrackResult( + func getTrackResult( forEvent event: String, params: [String: Any]? = nil, completion: @escaping (TrackResult) -> Void @@ -269,7 +269,7 @@ public extension Superwall { /// /// - Returns: A ``TrackInfoObjc`` object that contains information about the result of tracking an event. @available(swift, obsoleted: 1.0) - @objc static func getTrackInfo( + @objc func getTrackInfo( forEvent event: String, params: [String: Any]? = nil ) async -> TrackInfoObjc { @@ -282,7 +282,7 @@ public extension Superwall { /// - Parameters: /// - result: The dismissal result /// - completion: A completion block that gets called when the paywall is dismissed by the user, by way of purchasing, restoring or manually dismissing. Accepts a `Bool` that is `true` if the user purchased a product and `false` if not, a `String?` equal to the product id of the purchased product (if any) and a ``PaywallInfo`` object containing information about the paywall. - private static func onDismissConverter( + private func onDismissConverter( _ result: PaywallDismissedResult, completion: (PaywallDismissedResultStateObjc, String?, PaywallInfo) -> Void ) { @@ -296,7 +296,7 @@ public extension Superwall { } } - private static func onSkipConverter( + private func onSkipConverter( reason: PaywallSkippedReason, completion: ((PaywallSkippedReasonObjc, NSError) -> Void)? ) { diff --git a/Sources/SuperwallKit/Config/Assignments/Expression Evaluator/ExpressionEvaluator.swift b/Sources/SuperwallKit/Paywall/Presentation/Rule Logic/Expression Evaluator/ExpressionEvaluator.swift similarity index 91% rename from Sources/SuperwallKit/Config/Assignments/Expression Evaluator/ExpressionEvaluator.swift rename to Sources/SuperwallKit/Paywall/Presentation/Rule Logic/Expression Evaluator/ExpressionEvaluator.swift index b89e23758..d35dcc88f 100644 --- a/Sources/SuperwallKit/Config/Assignments/Expression Evaluator/ExpressionEvaluator.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Rule Logic/Expression Evaluator/ExpressionEvaluator.swift @@ -10,17 +10,14 @@ import JavaScriptCore struct ExpressionEvaluator { private let storage: Storage - private let identityManager: IdentityManager - private let deviceHelper: DeviceHelper + private unowned let factory: RuleAttributesFactory init( storage: Storage, - identityManager: IdentityManager, - deviceHelper: DeviceHelper + factory: RuleAttributesFactory ) { self.storage = storage - self.identityManager = identityManager - self.deviceHelper = deviceHelper + self.factory = factory } func evaluateExpression( @@ -88,9 +85,10 @@ struct ExpressionEvaluator { forRule rule: TriggerRule, withEventData eventData: EventData ) -> String? { + let ruleAttributes = factory.makeRuleAttributes() let values = JSON([ - "user": identityManager.userAttributes, - "device": deviceHelper.templateDevice.toDictionary(), + "user": ruleAttributes.user, + "device": ruleAttributes.device, "params": eventData.parameters ]) diff --git a/Sources/SuperwallKit/Config/Assignments/Expression Evaluator/ExpressionEvaluatorParams.swift b/Sources/SuperwallKit/Paywall/Presentation/Rule Logic/Expression Evaluator/ExpressionEvaluatorParams.swift similarity index 100% rename from Sources/SuperwallKit/Config/Assignments/Expression Evaluator/ExpressionEvaluatorParams.swift rename to Sources/SuperwallKit/Paywall/Presentation/Rule Logic/Expression Evaluator/ExpressionEvaluatorParams.swift diff --git a/Sources/SuperwallKit/Paywall/Presentation/Rule Logic/RuleAttributes.swift b/Sources/SuperwallKit/Paywall/Presentation/Rule Logic/RuleAttributes.swift new file mode 100644 index 000000000..896532507 --- /dev/null +++ b/Sources/SuperwallKit/Paywall/Presentation/Rule Logic/RuleAttributes.swift @@ -0,0 +1,13 @@ +// +// File.swift +// +// +// Created by Yusuf Tör on 27/01/2023. +// + +import Foundation + +struct RuleAttributes { + let user: [String: Any] + let device: [String: Any] +} diff --git a/Sources/SuperwallKit/Config/Assignments/AssignmentLogic.swift b/Sources/SuperwallKit/Paywall/Presentation/Rule Logic/RuleLogic.swift similarity index 95% rename from Sources/SuperwallKit/Config/Assignments/AssignmentLogic.swift rename to Sources/SuperwallKit/Paywall/Presentation/Rule Logic/RuleLogic.swift index fc5beef0b..401c11cc9 100644 --- a/Sources/SuperwallKit/Config/Assignments/AssignmentLogic.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Rule Logic/RuleLogic.swift @@ -12,11 +12,10 @@ struct ConfirmableAssignment: Equatable { let variant: Experiment.Variant } -struct AssignmentLogic { +struct RuleLogic { unowned let configManager: ConfigManager unowned let storage: Storage - unowned let identityManager: IdentityManager - unowned let deviceHelper: DeviceHelper + unowned let factory: RuleAttributesFactory struct Outcome { var confirmableAssignment: ConfirmableAssignment? @@ -122,8 +121,7 @@ struct AssignmentLogic { ) -> TriggerRule? { let expressionEvaluator = ExpressionEvaluator( storage: storage, - identityManager: identityManager, - deviceHelper: deviceHelper + factory: factory ) for rule in trigger.rules { if expressionEvaluator.evaluateExpression( diff --git a/Sources/SuperwallKit/Paywall Request/Operators/AddProductsOperator.swift b/Sources/SuperwallKit/Paywall/Request/Operators/AddProductsOperator.swift similarity index 81% rename from Sources/SuperwallKit/Paywall Request/Operators/AddProductsOperator.swift rename to Sources/SuperwallKit/Paywall/Request/Operators/AddProductsOperator.swift index eb03484ba..1a68ce3d5 100644 --- a/Sources/SuperwallKit/Paywall Request/Operators/AddProductsOperator.swift +++ b/Sources/SuperwallKit/Paywall/Request/Operators/AddProductsOperator.swift @@ -23,7 +23,7 @@ extension AnyPublisher where Output == PipelineData, Failure == Error { await trackProductsLoadFinish( paywall: input.paywall, event: input.request.eventData, - sessionEventsManager: input.request.injections.sessionEventsManager + sessionEventsManager: input.request.dependencyContainer.sessionEventsManager ) return input } @@ -34,7 +34,7 @@ extension AnyPublisher where Output == PipelineData, Failure == Error { private func getProducts(_ input: PipelineData) -> AnyPublisher { Future { do { - let result = try await input.request.injections.storeKitManager.getProducts( + let result = try await input.request.dependencyContainer.storeKitManager.getProducts( withIds: input.paywall.productIds, responseProducts: input.paywall.products, substituting: input.request.overrides.products @@ -47,7 +47,7 @@ extension AnyPublisher where Output == PipelineData, Failure == Error { fromProducts: result.products, productsById: result.productsById, isFreeTrialAvailableOverride: input.request.overrides.isFreeTrial, - isFreeTrialAvailable: input.request.injections.storeKitManager.isFreeTrialAvailable(for:) + isFreeTrialAvailable: input.request.dependencyContainer.storeKitManager.isFreeTrialAvailable(for:) ) paywall.swProducts = outcome.orderedSwProducts paywall.productVariables = outcome.productVariables @@ -62,12 +62,12 @@ extension AnyPublisher where Output == PipelineData, Failure == Error { input.paywall.productsLoadingInfo.failAt = Date() let paywallInfo = input.paywall.getInfo( fromEvent: input.request.eventData, - sessionEventsManager: input.request.injections.sessionEventsManager + sessionEventsManager: input.request.dependencyContainer.sessionEventsManager ) await trackProductLoadFail( paywallInfo: paywallInfo, event: input.request.eventData, - sessionEventsManager: input.request.injections.sessionEventsManager + sessionEventsManager: input.request.dependencyContainer.sessionEventsManager ) throw error } @@ -81,16 +81,16 @@ extension AnyPublisher where Output == PipelineData, Failure == Error { input.paywall.productsLoadingInfo.startAt = Date() let paywallInfo = input.paywall.getInfo( fromEvent: input.request.eventData, - sessionEventsManager: input.request.injections.sessionEventsManager + sessionEventsManager: input.request.dependencyContainer.sessionEventsManager ) let productLoadEvent = InternalSuperwallEvent.PaywallProductsLoad( state: .start, paywallInfo: paywallInfo, eventData: input.request.eventData ) - await Superwall.track(productLoadEvent) + await Superwall.shared.track(productLoadEvent) - await input.request.injections.sessionEventsManager.triggerSession.trackProductsLoad( + await input.request.dependencyContainer.sessionEventsManager.triggerSession.trackProductsLoad( forPaywallId: paywallInfo.databaseId, state: .start ) @@ -106,7 +106,7 @@ extension AnyPublisher where Output == PipelineData, Failure == Error { paywallInfo: paywallInfo, eventData: event ) - await Superwall.track(productLoadEvent) + await Superwall.shared.track(productLoadEvent) await sessionEventsManager.triggerSession.trackProductsLoad( forPaywallId: paywallInfo.databaseId, @@ -132,6 +132,6 @@ extension AnyPublisher where Output == PipelineData, Failure == Error { paywallInfo: paywallInfo, eventData: event ) - await Superwall.track(productLoadEvent) + await Superwall.shared.track(productLoadEvent) } } diff --git a/Sources/SuperwallKit/Paywall Request/Operators/RawResponseOperator.swift b/Sources/SuperwallKit/Paywall/Request/Operators/RawResponseOperator.swift similarity index 80% rename from Sources/SuperwallKit/Paywall Request/Operators/RawResponseOperator.swift rename to Sources/SuperwallKit/Paywall/Request/Operators/RawResponseOperator.swift index 94227587b..14623cb51 100644 --- a/Sources/SuperwallKit/Paywall Request/Operators/RawResponseOperator.swift +++ b/Sources/SuperwallKit/Paywall/Request/Operators/RawResponseOperator.swift @@ -14,7 +14,7 @@ extension AnyPublisher where Output == PaywallRequest, Failure == Error { await trackResponseStarted( paywallId: request.responseIdentifiers.paywallId, event: request.eventData, - sessionEventsManager: request.injections.sessionEventsManager + sessionEventsManager: request.dependencyContainer.sessionEventsManager ) return request } @@ -22,12 +22,12 @@ extension AnyPublisher where Output == PaywallRequest, Failure == Error { .asyncMap { let paywallInfo = $0.paywall.getInfo( fromEvent: $0.request.eventData, - sessionEventsManager: $0.request.injections.sessionEventsManager + sessionEventsManager: $0.request.dependencyContainer.sessionEventsManager ) await trackResponseLoaded( paywallInfo, event: $0.request.eventData, - sessionEventsManager: $0.request.injections.sessionEventsManager + sessionEventsManager: $0.request.dependencyContainer.sessionEventsManager ) return $0 } @@ -44,16 +44,16 @@ extension AnyPublisher where Output == PaywallRequest, Failure == Error { var paywall: Paywall do { - if let staticPaywall = request.injections.configManager.getStaticPaywall(withId: paywallId) { + if let staticPaywall = request.dependencyContainer.configManager.getStaticPaywall(withId: paywallId) { paywall = staticPaywall } else { - paywall = try await request.injections.network.getPaywall( + paywall = try await request.dependencyContainer.network.getPaywall( withId: paywallId, fromEvent: event ) } } catch { - await request.injections.sessionEventsManager.triggerSession.trackPaywallResponseLoad( + await request.dependencyContainer.sessionEventsManager.triggerSession.trackPaywallResponseLoad( forPaywallId: request.responseIdentifiers.paywallId, state: .fail ) @@ -87,7 +87,7 @@ extension AnyPublisher where Output == PaywallRequest, Failure == Error { state: .start, eventData: event ) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) } private func trackResponseLoaded( @@ -99,7 +99,7 @@ extension AnyPublisher where Output == PaywallRequest, Failure == Error { state: .complete(paywallInfo: paywallInfo), eventData: event ) - await Superwall.track(responseLoadEvent) + await Superwall.shared.track(responseLoadEvent) await sessionEventsManager.triggerSession.trackPaywallResponseLoad( forPaywallId: paywallInfo.databaseId, diff --git a/Sources/SuperwallKit/Paywall Request/PaywallLogic.swift b/Sources/SuperwallKit/Paywall/Request/PaywallLogic.swift similarity index 99% rename from Sources/SuperwallKit/Paywall Request/PaywallLogic.swift rename to Sources/SuperwallKit/Paywall/Request/PaywallLogic.swift index d9e36efa0..a7d6b3e46 100644 --- a/Sources/SuperwallKit/Paywall Request/PaywallLogic.swift +++ b/Sources/SuperwallKit/Paywall/Request/PaywallLogic.swift @@ -37,7 +37,7 @@ enum PaywallLogic { static func handlePaywallError( _ error: Error, forEvent event: EventData?, - trackEvent: @escaping (Trackable) async -> TrackingResult = Superwall.track + trackEvent: @escaping (Trackable) async -> TrackingResult = Superwall.shared.track ) -> NSError { if let error = error as? CustomURLSession.NetworkError, error == .notFound { diff --git a/Sources/SuperwallKit/Paywall Request/PaywallRequest.swift b/Sources/SuperwallKit/Paywall/Request/PaywallRequest.swift similarity index 72% rename from Sources/SuperwallKit/Paywall Request/PaywallRequest.swift rename to Sources/SuperwallKit/Paywall/Request/PaywallRequest.swift index a7a7f2a89..3c19ec518 100644 --- a/Sources/SuperwallKit/Paywall Request/PaywallRequest.swift +++ b/Sources/SuperwallKit/Paywall/Request/PaywallRequest.swift @@ -27,14 +27,7 @@ struct PaywallRequest { /// Overrides within the paywall. var overrides = Overrides() - struct Injections { - unowned let sessionEventsManager: SessionEventsManager - unowned let storeKitManager: StoreKitManager - unowned let configManager: ConfigManager - unowned let network: Network - unowned let debugManager: DebugManager - } - let injections: Injections + unowned let dependencyContainer: DependencyContainer /// The request publisher that fires just once. var publisher: AnyPublisher { diff --git a/Sources/SuperwallKit/Paywall Request/PaywallRequestManager.swift b/Sources/SuperwallKit/Paywall/Request/PaywallRequestManager.swift similarity index 82% rename from Sources/SuperwallKit/Paywall Request/PaywallRequestManager.swift rename to Sources/SuperwallKit/Paywall/Request/PaywallRequestManager.swift index 8dd8a4830..9d052517f 100644 --- a/Sources/SuperwallKit/Paywall Request/PaywallRequestManager.swift +++ b/Sources/SuperwallKit/Paywall/Request/PaywallRequestManager.swift @@ -8,22 +8,20 @@ import Foundation import Combine +/// Actor responsible for handling all paywall requests. actor PaywallRequestManager { - unowned let storeKitManager: StoreKitManager - - // swiftlint:disable implicitly_unwrapped_optional - unowned var deviceHelper: DeviceHelper! - // swiftlint:enable implicitly_unwrapped_optional + private unowned let storeKitManager: StoreKitManager + private unowned let factory: DeviceInfoFactory private var activeTasks: [String: Task] = [:] private var paywallsByHash: [String: Paywall] = [:] - init(storeKitManager: StoreKitManager) { + init( + storeKitManager: StoreKitManager, + factory: DeviceInfoFactory + ) { self.storeKitManager = storeKitManager - } - - func postInit(deviceHelper: DeviceHelper) { - self.deviceHelper = deviceHelper + self.factory = factory } /// Gets a paywall from a given request. @@ -34,14 +32,15 @@ actor PaywallRequestManager { /// - request: A request to get a paywall. /// - Returns A paywall. func getPaywall(from request: PaywallRequest) async throws -> Paywall { + let deviceInfo = factory.makeDeviceInfo() let requestHash = PaywallLogic.requestHash( identifier: request.responseIdentifiers.paywallId, event: request.eventData, - locale: deviceHelper.locale + locale: deviceInfo.locale ) let notSubstitutingProducts = request.overrides.products == nil - let debuggerNotLaunched = !request.injections.debugManager.isDebuggerLaunched + let debuggerNotLaunched = !request.dependencyContainer.debugManager.isDebuggerLaunched let shouldUseCache = notSubstitutingProducts && debuggerNotLaunched if var paywall = paywallsByHash[requestHash], diff --git a/Sources/SuperwallKit/Paywall View Controller/Loading/ActivityIndicatorView.swift b/Sources/SuperwallKit/Paywall/View Controller/Loading/ActivityIndicatorView.swift similarity index 100% rename from Sources/SuperwallKit/Paywall View Controller/Loading/ActivityIndicatorView.swift rename to Sources/SuperwallKit/Paywall/View Controller/Loading/ActivityIndicatorView.swift diff --git a/Sources/SuperwallKit/Paywall View Controller/Loading/Animations/BottomPaddingAnimation.swift b/Sources/SuperwallKit/Paywall/View Controller/Loading/Animations/BottomPaddingAnimation.swift similarity index 100% rename from Sources/SuperwallKit/Paywall View Controller/Loading/Animations/BottomPaddingAnimation.swift rename to Sources/SuperwallKit/Paywall/View Controller/Loading/Animations/BottomPaddingAnimation.swift diff --git a/Sources/SuperwallKit/Paywall View Controller/Loading/Animations/RotationAnimation.swift b/Sources/SuperwallKit/Paywall/View Controller/Loading/Animations/RotationAnimation.swift similarity index 100% rename from Sources/SuperwallKit/Paywall View Controller/Loading/Animations/RotationAnimation.swift rename to Sources/SuperwallKit/Paywall/View Controller/Loading/Animations/RotationAnimation.swift diff --git a/Sources/SuperwallKit/Paywall View Controller/Loading/Animations/ScaleAnimation.swift b/Sources/SuperwallKit/Paywall/View Controller/Loading/Animations/ScaleAnimation.swift similarity index 100% rename from Sources/SuperwallKit/Paywall View Controller/Loading/Animations/ScaleAnimation.swift rename to Sources/SuperwallKit/Paywall/View Controller/Loading/Animations/ScaleAnimation.swift diff --git a/Sources/SuperwallKit/Paywall View Controller/Loading/Animations/SpringAnimation.swift b/Sources/SuperwallKit/Paywall/View Controller/Loading/Animations/SpringAnimation.swift similarity index 100% rename from Sources/SuperwallKit/Paywall View Controller/Loading/Animations/SpringAnimation.swift rename to Sources/SuperwallKit/Paywall/View Controller/Loading/Animations/SpringAnimation.swift diff --git a/Sources/SuperwallKit/Paywall View Controller/Loading/DarkBlurredBackground.swift b/Sources/SuperwallKit/Paywall/View Controller/Loading/DarkBlurredBackground.swift similarity index 100% rename from Sources/SuperwallKit/Paywall View Controller/Loading/DarkBlurredBackground.swift rename to Sources/SuperwallKit/Paywall/View Controller/Loading/DarkBlurredBackground.swift diff --git a/Sources/SuperwallKit/Paywall View Controller/Loading/Listeners/HiddenListener.swift b/Sources/SuperwallKit/Paywall/View Controller/Loading/HiddenListener.swift similarity index 79% rename from Sources/SuperwallKit/Paywall View Controller/Loading/Listeners/HiddenListener.swift rename to Sources/SuperwallKit/Paywall/View Controller/Loading/HiddenListener.swift index e93409157..1b5313a8a 100644 --- a/Sources/SuperwallKit/Paywall View Controller/Loading/Listeners/HiddenListener.swift +++ b/Sources/SuperwallKit/Paywall/View Controller/Loading/HiddenListener.swift @@ -10,9 +10,11 @@ import SwiftUI struct HiddenListenerViewModifier: ViewModifier { let isHidden: Published.Publisher let model: LoadingModel + let maxPadding: CGFloat func body(content: Content) -> some View { content + .onAppear() .onReceive(isHidden) { isHidden in isHidden ? hide() : show() } @@ -22,25 +24,29 @@ struct HiddenListenerViewModifier: ViewModifier { model.isAnimating = true model.scaleAmount = 1 model.rotationAmount = 0 + model.padding = maxPadding } private func hide() { model.scaleAmount = 0.05 model.rotationAmount = .pi model.isAnimating = false + model.padding = 0 } } extension View { func listen( to isHidden: Published.Publisher, - fromModel model: LoadingModel + fromModel model: LoadingModel, + maxPadding: CGFloat ) -> some View { ModifiedContent( content: self, modifier: HiddenListenerViewModifier( isHidden: isHidden, - model: model + model: model, + maxPadding: maxPadding ) ) } diff --git a/Sources/SuperwallKit/Paywall/View Controller/Loading/LoadingModel.swift b/Sources/SuperwallKit/Paywall/View Controller/Loading/LoadingModel.swift new file mode 100644 index 000000000..2338d63f3 --- /dev/null +++ b/Sources/SuperwallKit/Paywall/View Controller/Loading/LoadingModel.swift @@ -0,0 +1,21 @@ +// +// File.swift +// +// +// Created by Yusuf Tör on 11/10/2022. +// + +import SwiftUI + +final class LoadingModel: ObservableObject { + @Published var isAnimating = false + @Published var scaleAmount = 0.05 + @Published var rotationAmount: CGFloat = .pi + @Published var padding: CGFloat = 0 + @Published var isHidden = false + private weak var delegate: LoadingDelegate? + + init(delegate: LoadingDelegate?) { + self.delegate = delegate + } +} diff --git a/Sources/SuperwallKit/Paywall View Controller/Loading/LoadingView.swift b/Sources/SuperwallKit/Paywall/View Controller/Loading/LoadingView.swift similarity index 92% rename from Sources/SuperwallKit/Paywall View Controller/Loading/LoadingView.swift rename to Sources/SuperwallKit/Paywall/View Controller/Loading/LoadingView.swift index f38b9fcf4..99bad9d4f 100644 --- a/Sources/SuperwallKit/Paywall View Controller/Loading/LoadingView.swift +++ b/Sources/SuperwallKit/Paywall/View Controller/Loading/LoadingView.swift @@ -20,14 +20,10 @@ struct LoadingView: View { .scaleAnimation(for: model.scaleAmount) .bottomPaddingAnimation(for: model.padding) .listen( - to: model.$movedUp, + to: model.$isHidden, fromModel: model, maxPadding: proxy.size.height / 2 ) - .listen( - to: model.$isHidden, - fromModel: model - ) .frame( maxWidth: .infinity, maxHeight: .infinity diff --git a/Sources/SuperwallKit/Paywall View Controller/Loading/LoadingViewController.swift b/Sources/SuperwallKit/Paywall/View Controller/Loading/LoadingViewController.swift similarity index 100% rename from Sources/SuperwallKit/Paywall View Controller/Loading/LoadingViewController.swift rename to Sources/SuperwallKit/Paywall/View Controller/Loading/LoadingViewController.swift diff --git a/Sources/SuperwallKit/Paywall View Controller/Loading/ShimmerView.swift b/Sources/SuperwallKit/Paywall/View Controller/Loading/ShimmerView.swift similarity index 100% rename from Sources/SuperwallKit/Paywall View Controller/Loading/ShimmerView.swift rename to Sources/SuperwallKit/Paywall/View Controller/Loading/ShimmerView.swift diff --git a/Sources/SuperwallKit/Paywall View Controller/PaywallViewController.swift b/Sources/SuperwallKit/Paywall/View Controller/PaywallViewController.swift similarity index 98% rename from Sources/SuperwallKit/Paywall View Controller/PaywallViewController.swift rename to Sources/SuperwallKit/Paywall/View Controller/PaywallViewController.swift index 347bcb693..a4a387499 100644 --- a/Sources/SuperwallKit/Paywall View Controller/PaywallViewController.swift +++ b/Sources/SuperwallKit/Paywall/View Controller/PaywallViewController.swift @@ -217,12 +217,12 @@ class PaywallViewController: UIViewController, SWWebViewDelegate, LoadingDelegat await sessionEventsManager.triggerSession.trackPaywallOpen() storage.trackPaywallOpen() let trackedEvent = await InternalSuperwallEvent.PaywallOpen(paywallInfo: paywallInfo) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) } nonisolated private func trackClose() async { let trackedEvent = await InternalSuperwallEvent.PaywallClose(paywallInfo: paywallInfo) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) await sessionEventsManager.triggerSession.trackPaywallClose() } @@ -265,16 +265,16 @@ class PaywallViewController: UIViewController, SWWebViewDelegate, LoadingDelegat Task(priority: .utility) { let trackedEvent = InternalSuperwallEvent.PaywallWebviewLoad( state: .start, - paywallInfo: paywallInfo + paywallInfo: self.paywallInfo ) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) await sessionEventsManager.triggerSession.trackWebviewLoad( forPaywallId: paywallInfo.databaseId, state: .start ) } - if Superwall.options.paywalls.useCachedTemplates { + if Superwall.shared.options.paywalls.useCachedTemplates { let request = URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad) webView.load(request) } else { @@ -350,7 +350,7 @@ class PaywallViewController: UIViewController, SWWebViewDelegate, LoadingDelegat private func addLoadingView() { guard - let background = Superwall.options.paywalls.transactionBackgroundView, + let background = Superwall.shared.options.paywalls.transactionBackgroundView, background == .spinner else { return @@ -407,7 +407,7 @@ class PaywallViewController: UIViewController, SWWebViewDelegate, LoadingDelegat state: .timeout, paywallInfo: self.paywallInfo ) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) } UIView.springAnimate(withDuration: 2) { diff --git a/Sources/SuperwallKit/Paywall View Controller/Push Transition/PushTransition.swift b/Sources/SuperwallKit/Paywall/View Controller/Push Transition/PushTransition.swift similarity index 100% rename from Sources/SuperwallKit/Paywall View Controller/Push Transition/PushTransition.swift rename to Sources/SuperwallKit/Paywall/View Controller/Push Transition/PushTransition.swift diff --git a/Sources/SuperwallKit/Paywall View Controller/Push Transition/PushTransitionDelegate.swift b/Sources/SuperwallKit/Paywall/View Controller/Push Transition/PushTransitionDelegate.swift similarity index 100% rename from Sources/SuperwallKit/Paywall View Controller/Push Transition/PushTransitionDelegate.swift rename to Sources/SuperwallKit/Paywall/View Controller/Push Transition/PushTransitionDelegate.swift diff --git a/Sources/SuperwallKit/Paywall View Controller/Push Transition/PushTransitionLogic.swift b/Sources/SuperwallKit/Paywall/View Controller/Push Transition/PushTransitionLogic.swift similarity index 100% rename from Sources/SuperwallKit/Paywall View Controller/Push Transition/PushTransitionLogic.swift rename to Sources/SuperwallKit/Paywall/View Controller/Push Transition/PushTransitionLogic.swift diff --git a/Sources/SuperwallKit/Paywall View Controller/Web View/Message Handling/PaywallMessage.swift b/Sources/SuperwallKit/Paywall/View Controller/Web View/Message Handling/PaywallMessage.swift similarity index 100% rename from Sources/SuperwallKit/Paywall View Controller/Web View/Message Handling/PaywallMessage.swift rename to Sources/SuperwallKit/Paywall/View Controller/Web View/Message Handling/PaywallMessage.swift diff --git a/Sources/SuperwallKit/Paywall View Controller/Web View/Message Handling/PaywallMessageHandler.swift b/Sources/SuperwallKit/Paywall/View Controller/Web View/Message Handling/PaywallMessageHandler.swift similarity index 97% rename from Sources/SuperwallKit/Paywall View Controller/Web View/Message Handling/PaywallMessageHandler.swift rename to Sources/SuperwallKit/Paywall/View Controller/Web View/Message Handling/PaywallMessageHandler.swift index aafc59c49..7774d458c 100644 --- a/Sources/SuperwallKit/Paywall View Controller/Web View/Message Handling/PaywallMessageHandler.swift +++ b/Sources/SuperwallKit/Paywall/View Controller/Web View/Message Handling/PaywallMessageHandler.swift @@ -128,7 +128,7 @@ final class PaywallMessageHandler: WebEventDelegate { state: .complete, paywallInfo: paywallInfo ) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) await sessionEventsManager.triggerSession.trackWebviewLoad( forPaywallId: paywallInfo.databaseId, @@ -264,10 +264,10 @@ final class PaywallMessageHandler: WebEventDelegate { } private func hapticFeedback() { - guard Superwall.options.paywalls.isHapticFeedbackEnabled else { + guard Superwall.shared.options.paywalls.isHapticFeedbackEnabled else { return } - if Superwall.options.isGameControllerEnabled { + if Superwall.shared.options.isGameControllerEnabled { return } UIImpactFeedbackGenerator().impactOccurred(intensity: 0.7) diff --git a/Sources/SuperwallKit/Paywall View Controller/Web View/Message Handling/PaywallWebEvent.swift b/Sources/SuperwallKit/Paywall/View Controller/Web View/Message Handling/PaywallWebEvent.swift similarity index 100% rename from Sources/SuperwallKit/Paywall View Controller/Web View/Message Handling/PaywallWebEvent.swift rename to Sources/SuperwallKit/Paywall/View Controller/Web View/Message Handling/PaywallWebEvent.swift diff --git a/Sources/SuperwallKit/Paywall View Controller/Web View/Message Handling/RawWebMessageHandler.swift b/Sources/SuperwallKit/Paywall/View Controller/Web View/Message Handling/RawWebMessageHandler.swift similarity index 100% rename from Sources/SuperwallKit/Paywall View Controller/Web View/Message Handling/RawWebMessageHandler.swift rename to Sources/SuperwallKit/Paywall/View Controller/Web View/Message Handling/RawWebMessageHandler.swift diff --git a/Sources/SuperwallKit/Paywall View Controller/Web View/SWWebView.swift b/Sources/SuperwallKit/Paywall/View Controller/Web View/SWWebView.swift similarity index 98% rename from Sources/SuperwallKit/Paywall View Controller/Web View/SWWebView.swift rename to Sources/SuperwallKit/Paywall/View Controller/Web View/SWWebView.swift index 28e57909a..0c56badcc 100644 --- a/Sources/SuperwallKit/Paywall View Controller/Web View/SWWebView.swift +++ b/Sources/SuperwallKit/Paywall/View Controller/Web View/SWWebView.swift @@ -138,6 +138,6 @@ extension SWWebView: WKNavigationDelegate { state: .fail, paywallInfo: paywallInfo ) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) } } diff --git a/Sources/SuperwallKit/Paywall View Controller/Web View/Templating/Models/DeviceTemplate.swift b/Sources/SuperwallKit/Paywall/View Controller/Web View/Templating/Models/DeviceTemplate.swift similarity index 100% rename from Sources/SuperwallKit/Paywall View Controller/Web View/Templating/Models/DeviceTemplate.swift rename to Sources/SuperwallKit/Paywall/View Controller/Web View/Templating/Models/DeviceTemplate.swift diff --git a/Sources/SuperwallKit/Paywall View Controller/Web View/Templating/Models/FreeTrialTemplate.swift b/Sources/SuperwallKit/Paywall/View Controller/Web View/Templating/Models/FreeTrialTemplate.swift similarity index 100% rename from Sources/SuperwallKit/Paywall View Controller/Web View/Templating/Models/FreeTrialTemplate.swift rename to Sources/SuperwallKit/Paywall/View Controller/Web View/Templating/Models/FreeTrialTemplate.swift diff --git a/Sources/SuperwallKit/Paywall View Controller/Web View/Templating/Models/ProductTemplate.swift b/Sources/SuperwallKit/Paywall/View Controller/Web View/Templating/Models/ProductTemplate.swift similarity index 100% rename from Sources/SuperwallKit/Paywall View Controller/Web View/Templating/Models/ProductTemplate.swift rename to Sources/SuperwallKit/Paywall/View Controller/Web View/Templating/Models/ProductTemplate.swift diff --git a/Sources/SuperwallKit/Paywall View Controller/Web View/Templating/Models/Variables.swift b/Sources/SuperwallKit/Paywall/View Controller/Web View/Templating/Models/Variables.swift similarity index 100% rename from Sources/SuperwallKit/Paywall View Controller/Web View/Templating/Models/Variables.swift rename to Sources/SuperwallKit/Paywall/View Controller/Web View/Templating/Models/Variables.swift diff --git a/Sources/SuperwallKit/Paywall View Controller/Web View/Templating/TemplateLogic.swift b/Sources/SuperwallKit/Paywall/View Controller/Web View/Templating/TemplateLogic.swift similarity index 96% rename from Sources/SuperwallKit/Paywall View Controller/Web View/Templating/TemplateLogic.swift rename to Sources/SuperwallKit/Paywall/View Controller/Web View/Templating/TemplateLogic.swift index 43b1ff216..412bf3764 100644 --- a/Sources/SuperwallKit/Paywall View Controller/Web View/Templating/TemplateLogic.swift +++ b/Sources/SuperwallKit/Paywall/View Controller/Web View/Templating/TemplateLogic.swift @@ -61,7 +61,7 @@ enum TemplateLogic { var variables: [String: Any] = [:] for variable in swProductTemplateVariables { - variables[variable.type.rawValue] = JSON(variable.attributes) + variables[variable.type.description] = JSON(variable.attributes) } // swiftlint:disable:next array_constructor diff --git a/Sources/SuperwallKit/Storage/Core Data/CoreDataStack.swift b/Sources/SuperwallKit/Storage/Core Data/CoreDataStack.swift index f3828bbf5..e31ed9da0 100644 --- a/Sources/SuperwallKit/Storage/Core Data/CoreDataStack.swift +++ b/Sources/SuperwallKit/Storage/Core Data/CoreDataStack.swift @@ -34,15 +34,20 @@ class CoreDataStack { return model }() - lazy var persistentContainer: NSPersistentContainer = { - let container = NSPersistentContainer( + var persistentContainer: NSPersistentContainer + let backgroundContext: NSManagedObjectContext + let mainContext: NSManagedObjectContext + + init() { + // First load persistent container + let persistentContainer = NSPersistentContainer( name: Self.modelName, managedObjectModel: Self.managedObject ) let dispatchGroup = DispatchGroup() dispatchGroup.enter() - container.loadPersistentStores { _, error in + persistentContainer.loadPersistentStores { _, error in if let error = error as NSError? { Logger.debug( logLevel: .error, @@ -55,20 +60,17 @@ class CoreDataStack { dispatchGroup.leave() } dispatchGroup.wait() - return container - }() + self.persistentContainer = persistentContainer - lazy var backgroundContext: NSManagedObjectContext = { - let context = persistentContainer.newBackgroundContext() - context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump - return context - }() + // Then load background and main context + let backgroundContext = persistentContainer.newBackgroundContext() + backgroundContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump + self.backgroundContext = backgroundContext - lazy var mainContext: NSManagedObjectContext = { let mainContext = persistentContainer.viewContext mainContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump - return mainContext - }() + self.mainContext = mainContext + } func saveContext( _ context: NSManagedObjectContext, diff --git a/Sources/SuperwallKit/Storage/EventsQueue.swift b/Sources/SuperwallKit/Storage/EventsQueue.swift index 8e21c0908..d9120fd23 100644 --- a/Sources/SuperwallKit/Storage/EventsQueue.swift +++ b/Sources/SuperwallKit/Storage/EventsQueue.swift @@ -73,7 +73,7 @@ actor EventsQueue { } private func externalDataCollectionAllowed(from event: Trackable) -> Bool { - if Superwall.options.isExternalDataCollectionEnabled { + if Superwall.shared.options.isExternalDataCollectionEnabled { return true } if event is InternalSuperwallEvent.TriggerFire diff --git a/Sources/SuperwallKit/Storage/Storage.swift b/Sources/SuperwallKit/Storage/Storage.swift index 5fb69d5b3..438acb65c 100644 --- a/Sources/SuperwallKit/Storage/Storage.swift +++ b/Sources/SuperwallKit/Storage/Storage.swift @@ -23,30 +23,25 @@ class Storage { /// This means that we'll need to wait for assignments before firing triggers. var neverCalledStaticConfig = false - // swiftlint:disable implicitly_unwrapped_optional - unowned var deviceHelper: DeviceHelper! - // swiftlint:enable implicitly_unwrapped_optional - /// The confirmed assignments for the user loaded from the cache. private var confirmedAssignments: [Experiment.ID: Experiment.Variant]? /// The disk cache. private let cache: Cache + private unowned let factory: DeviceInfoFactory + // MARK: - Configuration - /// **NOTE**: After init'ing, call `postInit` init( + factory: DeviceInfoFactory, cache: Cache = Cache(), coreDataManager: CoreDataManager = CoreDataManager() ) { self.cache = cache self.coreDataManager = coreDataManager self.didTrackFirstSeen = cache.read(DidTrackFirstSeen.self) == true - } - - func postInit(deviceHelper: DeviceHelper) { - self.deviceHelper = deviceHelper + self.factory = factory } func configure(apiKey: String) { @@ -95,7 +90,7 @@ class Storage { } Task { - await Superwall.track(InternalSuperwallEvent.FirstSeen()) + await Superwall.shared.track(InternalSuperwallEvent.FirstSeen()) } save(true, forType: DidTrackFirstSeen.self) didTrackFirstSeen = true @@ -103,14 +98,16 @@ class Storage { /// Records the app install func recordAppInstall( - trackEvent: @escaping (Trackable) async -> TrackingResult = Superwall.track + trackEvent: @escaping (Trackable) async -> TrackingResult = Superwall.shared.track ) { let didTrackAppInstall = get(DidTrackAppInstall.self) ?? false if didTrackAppInstall { return } Task { - _ = await trackEvent(InternalSuperwallEvent.AppInstall(deviceHelper: deviceHelper)) + let deviceInfo = factory.makeDeviceInfo() + let event = InternalSuperwallEvent.AppInstall(appInstalledAtString: deviceInfo.appInstalledAtString) + _ = await trackEvent(event) } save(true, forType: DidTrackAppInstall.self) } diff --git a/Sources/SuperwallKit/StoreKit/Abstractions/StoreProduct/Discount/StoreProductDiscount.swift b/Sources/SuperwallKit/StoreKit/Abstractions/StoreProduct/Discount/StoreProductDiscount.swift index a61be7623..201aad8b8 100644 --- a/Sources/SuperwallKit/StoreKit/Abstractions/StoreProduct/Discount/StoreProductDiscount.swift +++ b/Sources/SuperwallKit/StoreKit/Abstractions/StoreProduct/Discount/StoreProductDiscount.swift @@ -61,6 +61,14 @@ public final class StoreProductDiscount: NSObject, StoreProductDiscountType { public var offerIdentifier: String? { self.discount.offerIdentifier } public var currencyCode: String? { self.discount.currencyCode } @nonobjc public var price: Decimal { self.discount.price } + + /// The discount price of the product in the local currency. + @objc(price) + @available(swift, obsoleted: 1.0) + public var objcPrice: NSDecimalNumber { + return price as NSDecimalNumber + } + public var localizedPriceString: String { self.discount.localizedPriceString } public var paymentMode: PaymentMode { self.discount.paymentMode } public var subscriptionPeriod: SubscriptionPeriod { self.discount.subscriptionPeriod } @@ -86,16 +94,7 @@ public final class StoreProductDiscount: NSObject, StoreProductDiscountType { } extension StoreProductDiscount { - /// The discount price of the product in the local currency. - /// - Note: this is meant for Objective-C. For Swift, use ``price`` instead. - @objc(price) - public var priceDecimalNumber: NSDecimalNumber { - return self.price as NSDecimalNumber - } -} - -extension StoreProductDiscount { - /// Used to represent `StoreProductDiscount/id`. + /// Used to represent ``id``. public struct Data: Hashable { private var offerIdentifier: String? private var currencyCode: String? diff --git a/Sources/SuperwallKit/StoreKit/Abstractions/StoreProduct/StoreProduct.swift b/Sources/SuperwallKit/StoreKit/Abstractions/StoreProduct/StoreProduct.swift index abd35fff0..0c0dc9d1c 100644 --- a/Sources/SuperwallKit/StoreKit/Abstractions/StoreProduct/StoreProduct.swift +++ b/Sources/SuperwallKit/StoreKit/Abstractions/StoreProduct/StoreProduct.swift @@ -37,14 +37,17 @@ public final class StoreProduct: NSObject, StoreProductType { return (product as? SK2StoreProduct)?.underlyingSK2Product } + /// The product identifier public var productIdentifier: String { product.productIdentifier } + /// The product's subscription group id public var subscriptionGroupIdentifier: String? { return product.subscriptionGroupIdentifier } + /// All the attributes that can be referenced in Superwall rules. public var attributes: [String: String] { return [ "rawPrice": "\(price)", @@ -75,26 +78,35 @@ public final class StoreProduct: NSObject, StoreProductType { ] } + /// The JSON representation of ``attributes`` var attributesJson: JSON { return JSON(attributes) } + /// An internally used Superwall representation of ``attributes``. var swProductTemplateVariablesJson: JSON { product.swProductTemplateVariablesJson } + /// An internally used Superwall representation of the product. var swProduct: SWProduct { product.swProduct } + /// The localized price. public var localizedPrice: String { product.localizedPrice } + /// The localized subscription period. public var localizedSubscriptionPeriod: String { product.localizedSubscriptionPeriod } + /// The subscription period unit, e.g. week. + /// + /// This returns week, day, month, 2 months, quarter, 6 months and year + /// depending on the number of units. public var period: String { product.period } @@ -103,139 +115,180 @@ public final class StoreProduct: NSObject, StoreProductType { product.periodly } + /// The number of weeks in the product's subscription period. public var periodWeeks: Int { product.periodWeeks } + /// The string value of the number of weeks in the product's subscription period. public var periodWeeksString: String { product.periodWeeksString } + /// The number of months in the product's subscription period. public var periodMonths: Int { product.periodMonths } + /// The string value of the number of months in the product's subscription period. public var periodMonthsString: String { product.periodMonthsString } + /// The number of years in the product's subscription period. public var periodYears: Int { product.periodYears } + /// The string value of the number of years in the product's subscription period. public var periodYearsString: String { product.periodYearsString } + /// The number of days in the product's subscription period. public var periodDays: Int { product.periodDays } + /// The string value of the number of days in the product's subscription period. public var periodDaysString: String { product.periodDaysString } + /// The product's localized daily price. public var dailyPrice: String { product.dailyPrice } + /// The product's localized weekly price. public var weeklyPrice: String { product.weeklyPrice } + /// The product's localized monthly price. public var monthlyPrice: String { product.monthlyPrice } + /// The product's localized yearly price. public var yearlyPrice: String { product.yearlyPrice } + /// A boolean indicating whether the product has an introductory price. public var hasFreeTrial: Bool { product.hasFreeTrial } + /// The product's trial period end date. public var trialPeriodEndDate: Date? { product.trialPeriodEndDate } + /// The product's trial period end date formatted using `DateFormatter.Style.medium` public var trialPeriodEndDateString: String { product.trialPeriodEndDateString } + /// The product's introductory price duration in days. public var trialPeriodDays: Int { product.trialPeriodDays } + /// The product's string value of the introductory price duration in days. public var trialPeriodDaysString: String { product.trialPeriodDaysString } + /// The product's introductory price duration in weeks. public var trialPeriodWeeks: Int { product.trialPeriodWeeks } + /// The product's string value of the introductory price duration in weeks. public var trialPeriodWeeksString: String { product.trialPeriodWeeksString } + /// The product's introductory price duration in months. public var trialPeriodMonths: Int { product.trialPeriodMonths } + /// The product's string value of the introductory price duration in months. public var trialPeriodMonthsString: String { product.trialPeriodMonthsString } + /// The product's introductory price duration in years. public var trialPeriodYears: Int { product.trialPeriodYears } + /// The product's string value of the introductory price duration in years. public var trialPeriodYearsString: String { product.trialPeriodYearsString } + /// The product's introductory price duration in days, e.g. 7-day. public var trialPeriodText: String { product.trialPeriodText } + /// The product's locale. public var locale: String { product.locale } + /// The language code of the product's locale. public var languageCode: String? { product.languageCode } + /// The currency code of the product's locale. public var currencyCode: String? { product.currencyCode } + /// The currency symbol of the product's locale. public var currencySymbol: String? { product.currencySymbol } + /// A boolean that indicates whether the product is family shareable. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 8.0, *) public var isFamilyShareable: Bool { product.isFamilyShareable } + /// The region code of the product's price locale. public var regionCode: String? { product.regionCode } - public var price: Decimal { + /// The price of the product in the local currency. + @objc(price) + @available(swift, obsoleted: 1.0) + public var objcPrice: NSDecimalNumber { + return product.price as NSDecimalNumber + } + + /// The price of the product in the local currency. + @nonobjc public var price: Decimal { product.price } + /// The product's subscription period. public var subscriptionPeriod: SubscriptionPeriod? { product.subscriptionPeriod } + /// The product's introductory discount. public var introductoryDiscount: StoreProductDiscount? { product.introductoryDiscount } + /// The discounts associated with the product. public var discounts: [StoreProductDiscount] { product.discounts } @@ -245,7 +298,6 @@ public final class StoreProduct: NSObject, StoreProductType { } /// Designated initializer. - /// - SeeAlso: `StoreProduct/from(product:)` to wrap an instance of `StoreProduct` private init(_ product: StoreProductType) { self.product = product } diff --git a/Sources/SuperwallKit/StoreKit/Coordinator/CoordinatorProtocols.swift b/Sources/SuperwallKit/StoreKit/Coordinator/CoordinatorProtocols.swift index b998ca1f6..852430794 100644 --- a/Sources/SuperwallKit/StoreKit/Coordinator/CoordinatorProtocols.swift +++ b/Sources/SuperwallKit/StoreKit/Coordinator/CoordinatorProtocols.swift @@ -9,9 +9,9 @@ import Foundation protocol TransactionChecker: AnyObject { /// Gets and validates a transaction of a product. - func getAndValidateTransaction( + func getAndValidateLatestTransaction( of productId: String, - since purchaseStartDate: Date + since purchasedAt: Date? ) async throws -> StoreTransaction } diff --git a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ReceiptManager.swift b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ReceiptManager.swift index ace4b77bf..bb2dbb4b0 100644 --- a/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ReceiptManager.swift +++ b/Sources/SuperwallKit/StoreKit/Products/Receipt Manager/ReceiptManager.swift @@ -101,16 +101,6 @@ final class ReceiptManager: NSObject { return isRefreshed } - func addSubscriptionGroupId(_ id: String?) { - guard let id = id else { - return - } - if var purchasedSubscriptionGroupIds = purchasedSubscriptionGroupIds { - purchasedSubscriptionGroupIds.insert(id) - } - purchasedSubscriptionGroupIds = [id] - } - /// Determines whether the purchases already contain the given product ID. func hasPurchasedProduct(withId productId: String) -> Bool { return purchases.first { $0.productIdentifier == productId } != nil diff --git a/Sources/SuperwallKit/StoreKit/StoreKitManager.swift b/Sources/SuperwallKit/StoreKit/StoreKitManager.swift index 9789777e0..0b75442df 100644 --- a/Sources/SuperwallKit/StoreKit/StoreKitManager.swift +++ b/Sources/SuperwallKit/StoreKit/StoreKitManager.swift @@ -2,46 +2,36 @@ import Foundation import Combine final class StoreKitManager { - var productsById: [String: StoreProduct] = [:] + /// Coordinates: The purchasing, restoring and retrieving of products; the checking + /// of transactions; and the determining of the user's subscription status. + lazy var coordinator = factory.makeStoreKitCoordinator() + private unowned let factory: StoreKitCoordinatorFactory + private lazy var receiptManager = ReceiptManager(delegate: self) + + private(set) var productsById: [String: StoreProduct] = [:] private struct ProductProcessingResult { let productIdsToLoad: Set let substituteProductsById: [String: StoreProduct] let products: [Product] } - private let factory: StoreKitCoordinatorFactory - // swiftlint:disable implicitly_unwrapped_optional - /// Coordinates: The purchasing, restoring and retrieving of products; the checking - /// of transactions; and the determining of the user's subscription status. - var coordinator: StoreKitCoordinator! - private var receiptManager: ReceiptManager! - // swiftlint:enable implicitly_unwrapped_optional - - /// **NOTE:** Remember to call `postInit()` after init init(factory: StoreKitCoordinatorFactory) { self.factory = factory } - /// Separated from init to stop circular references during init. - func postInit() { - coordinator = factory.makeStoreKitCoordinator() - receiptManager = ReceiptManager(delegate: self) - } - func getProductVariables(for paywall: Paywall) async -> [ProductVariable] { guard let output = try? await getProducts(withIds: paywall.productIds) else { return [] } - var variables: [ProductVariable] = [] - - for product in paywall.products { - if let storeProduct = output.productsById[product.id] { - let variable = ProductVariable( - type: product.type, - attributes: storeProduct.attributesJson - ) - variables.append(variable) + + let variables = paywall.products.compactMap { product -> ProductVariable? in + guard let storeProduct = output.productsById[product.id] else { + return nil } + return ProductVariable( + type: product.type, + attributes: storeProduct.attributesJson + ) } return variables diff --git a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserLogic.swift b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserLogic.swift index b623d871a..ae53fdc81 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserLogic.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserLogic.swift @@ -10,21 +10,21 @@ import StoreKit enum ProductPurchaserLogic { static func validate( - lastTransaction: SKPaymentTransaction, + latestTransaction: SKPaymentTransaction, withProductId productId: String, - since startAt: Date + since purchasedAt: Date? ) throws { - guard lastTransaction.payment.productIdentifier == productId else { + guard latestTransaction.payment.productIdentifier == productId else { throw PurchaseError.noTransactionDetected } - guard lastTransaction.transactionState == .purchased else { + guard latestTransaction.transactionState == .purchased else { throw PurchaseError.noTransactionDetected } - guard let transactionDate = lastTransaction.transactionDate else { - throw PurchaseError.noTransactionDetected - } - guard transactionDate >= startAt else { - throw PurchaseError.noTransactionDetected + if let purchasedAt = purchasedAt, + let latestTransactionDate = latestTransaction.transactionDate { + guard latestTransactionDate >= purchasedAt else { + throw PurchaseError.noTransactionDetected + } } // Validation diff --git a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserSK1.swift b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserSK1.swift index f4af1cba3..ded58fa8e 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserSK1.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/ProductPurchaserSK1.swift @@ -12,12 +12,11 @@ final class ProductPurchaserSK1: NSObject { // MARK: Purchasing final class Purchasing { var completion: ((PurchaseResult) -> Void)? - var transaction: SKPaymentTransaction? var productId: String? } private let purchasing = Purchasing() - // MARK: - Restoration + // MARK: Restoration final class Restoration { var completion: ((Bool) -> Void)? var dispatchGroup = DispatchGroup() @@ -29,6 +28,9 @@ final class ProductPurchaserSK1: NSObject { } private let restoration = Restoration() + /// The latest transaction, used for purchasing and verifying purchases. + private var latestTransaction: SKPaymentTransaction? + // MARK: Dependencies private unowned let storeKitManager: StoreKitManager private unowned let sessionEventsManager: SessionEventsManager @@ -88,21 +90,21 @@ extension ProductPurchaserSK1: TransactionChecker { /// The receipts are updated on successful purchase. /// /// Read more in [Apple's docs](https://developer.apple.com/documentation/storekit/in-app_purchase/original_api_for_in-app_purchase/choosing_a_receipt_validation_technique#//apple_ref/doc/uid/TP40010573). - func getAndValidateTransaction( + func getAndValidateLatestTransaction( of productId: String, - since startAt: Date + since purchasedAt: Date? ) async throws -> StoreTransaction { - guard let transaction = purchasing.transaction else { + guard let latestTransaction = latestTransaction else { throw PurchaseError.noTransactionDetected } try ProductPurchaserLogic.validate( - lastTransaction: transaction, + latestTransaction: latestTransaction, withProductId: productId, - since: startAt + since: purchasedAt ) - let storeTransaction = await factory.makeStoreTransaction(from: transaction) - self.purchasing.transaction = nil + let storeTransaction = await factory.makeStoreTransaction(from: latestTransaction) + self.latestTransaction = nil return storeTransaction } @@ -162,6 +164,7 @@ extension ProductPurchaserSK1: SKPaymentTransactionObserver { let isPaywallPresented = await Superwall.shared.isPaywallPresented let paywallViewController = await Superwall.shared.paywallViewController for transaction in transactions { + latestTransaction = transaction await checkForTimeout(of: transaction, in: paywallViewController) updatePurchaseCompletionBlock(for: transaction) await checkForRestoration(transaction, isPaywallPresented: isPaywallPresented) @@ -200,7 +203,7 @@ extension ProductPurchaserSK1: SKPaymentTransactionObserver { product: nil, model: nil ) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) default: break } @@ -219,20 +222,28 @@ extension ProductPurchaserSK1: SKPaymentTransactionObserver { switch transaction.transactionState { case .purchased: - purchasing.transaction = transaction purchasing.completion?(.purchased) case .failed: if let error = transaction.error { if let error = error as? SKError { switch error.code { - case .overlayTimeout, - .paymentCancelled, + case .paymentCancelled, .overlayCancelled: purchasing.completion?(.cancelled) return default: break } + + if #available(iOS 14, *) { + switch error.code { + case .overlayTimeout: + purchasing.completion?(.cancelled) + return + default: + break + } + } } purchasing.completion?(.failed(error)) } diff --git a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchaseManager.swift b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchaseManager.swift index a3ef7b927..371debbd1 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchaseManager.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchaseManager.swift @@ -37,6 +37,7 @@ struct PurchaseManager { /// Purchases the product and then checks for a transaction, func purchase(product: StoreProduct) async -> InternalPurchaseResult { let purchaseStartAt = Date() + let result = await storeKitManager.coordinator.productPurchaser.purchase(product: product) if let transactionResult = await checkForTransaction( @@ -68,9 +69,18 @@ struct PurchaseManager { startAt: Date ) async -> InternalPurchaseResult? { do { - let transaction = try await storeKitManager.coordinator.txnChecker.getAndValidateTransaction( + // If the product was reported purchased, we just check it's valid. + // This is because someone may have reinstalled app and now gone to + // purchase without restoring. In this case, it returns a purchased + // product immediately whose date is in the past. + // + // If the product wasn't reported purchase, we need to see if any + // transactions came in since we made the purchase request. + + // TODO: What happens if it's pending and they do the flow above? + let transaction = try await storeKitManager.coordinator.txnChecker.getAndValidateLatestTransaction( of: product.productIdentifier, - since: startAt + since: result == .purchased ? nil : startAt ) return .purchased(transaction) } catch { diff --git a/Sources/SuperwallKit/StoreKit/Transactions/RestorationHandler.swift b/Sources/SuperwallKit/StoreKit/Transactions/RestorationManager.swift similarity index 76% rename from Sources/SuperwallKit/StoreKit/Transactions/RestorationHandler.swift rename to Sources/SuperwallKit/StoreKit/Transactions/RestorationManager.swift index 7dcaceecf..3212f20ee 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/RestorationHandler.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/RestorationManager.swift @@ -8,19 +8,16 @@ import Foundation import StoreKit -final class RestorationHandler { +final class RestorationManager { private unowned let storeKitManager: StoreKitManager private unowned let sessionEventsManager: SessionEventsManager - private let superwall: Superwall.Type init( storeKitManager: StoreKitManager, - sessionEventsManager: SessionEventsManager, - superwall: Superwall.Type = Superwall.self + sessionEventsManager: SessionEventsManager ) { self.storeKitManager = storeKitManager self.sessionEventsManager = sessionEventsManager - self.superwall = superwall } @MainActor @@ -62,9 +59,9 @@ final class RestorationHandler { ) paywallViewController.presentAlert( - title: superwall.options.paywalls.restoreFailed.title, - message: superwall.options.paywalls.restoreFailed.message, - closeActionTitle: superwall.options.paywalls.restoreFailed.closeButtonTitle + title: Superwall.shared.options.paywalls.restoreFailed.title, + message: Superwall.shared.options.paywalls.restoreFailed.message, + closeActionTitle: Superwall.shared.options.paywalls.restoreFailed.closeButtonTitle ) } } @@ -79,11 +76,11 @@ final class RestorationHandler { product: nil, model: nil ) - await self.superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) } - if Superwall.options.paywalls.automaticallyDismiss { - superwall.shared.dismiss(paywallViewController, state: .restored) + if Superwall.shared.options.paywalls.automaticallyDismiss { + Superwall.shared.dismiss(paywallViewController, state: .restored) } } } diff --git a/Sources/SuperwallKit/StoreKit/Transactions/TransactionErrorLogic.swift b/Sources/SuperwallKit/StoreKit/Transactions/TransactionErrorLogic.swift index 1f2399027..2a2914b26 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/TransactionErrorLogic.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/TransactionErrorLogic.swift @@ -29,15 +29,24 @@ enum TransactionErrorLogic { break } } + if let error = error as? SKError { switch error.code { case .overlayCancelled, - .paymentCancelled, - .overlayTimeout: + .paymentCancelled: return .cancelled default: break } + + if #available(iOS 14, *) { + switch error.code { + case .overlayTimeout: + return .cancelled + default: + break + } + } } return .presentAlert diff --git a/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift b/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift index ecd042e25..baeac17b6 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift @@ -101,7 +101,7 @@ final class TransactionManager { product: product, model: nil ) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) } lastPaywallViewController = paywallViewController @@ -136,7 +136,7 @@ final class TransactionManager { product: product ) - if Superwall.options.paywalls.automaticallyDismiss { + if Superwall.shared.options.paywalls.automaticallyDismiss { await Superwall.shared.dismiss( paywallViewController, state: .purchased(productId: product.productIdentifier) @@ -165,7 +165,7 @@ final class TransactionManager { product: product, model: nil ) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) await self.sessionEventsManager.triggerSession.trackTransactionAbandon() } @@ -191,7 +191,7 @@ final class TransactionManager { product: nil, model: nil ) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) await self.sessionEventsManager.triggerSession.trackPendingTransaction() } @@ -225,7 +225,7 @@ final class TransactionManager { product: product, model: nil ) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) await self.sessionEventsManager.triggerSession.trackTransactionError() } @@ -260,14 +260,14 @@ final class TransactionManager { product: product, model: transaction ) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) if product.subscriptionPeriod == nil { let trackedEvent = InternalSuperwallEvent.NonRecurringProductPurchase( paywallInfo: paywallInfo, product: product ) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) } if didStartFreeTrial { @@ -275,13 +275,13 @@ final class TransactionManager { paywallInfo: paywallInfo, product: product ) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) } else { let trackedEvent = InternalSuperwallEvent.SubscriptionStart( paywallInfo: paywallInfo, product: product ) - await Superwall.track(trackedEvent) + await Superwall.shared.track(trackedEvent) } } lastPaywallViewController = nil diff --git a/Sources/SuperwallKit/StoreKit/Transactions/TransactionVerifierSK2.swift b/Sources/SuperwallKit/StoreKit/Transactions/TransactionVerifierSK2.swift index f9617a68d..26add0536 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/TransactionVerifierSK2.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/TransactionVerifierSK2.swift @@ -19,16 +19,18 @@ final class TransactionVerifierSK2: TransactionChecker { /// /// We need this function because on iOS 15+, the `Transaction.updates` listener doesn't notify us /// of transactions for recent purchases. - func getAndValidateTransaction( + func getAndValidateLatestTransaction( of productId: String, - since purchaseStartDate: Date + since purchaseStartDate: Date? = nil ) async throws -> StoreTransaction { let transaction = await Transaction.latest(for: productId) guard case let .verified(transaction) = transaction else { throw PurchaseError.unverifiedTransaction } - guard transaction.purchaseDate >= purchaseStartDate else { - throw PurchaseError.noTransactionDetected + if let purchaseStartDate = purchaseStartDate { + guard transaction.purchaseDate >= purchaseStartDate else { + throw PurchaseError.noTransactionDetected + } } return await factory.makeStoreTransaction(from: transaction) } diff --git a/Sources/SuperwallKit/Superwall.swift b/Sources/SuperwallKit/Superwall.swift index 2bcb924a0..3aebf0b2b 100644 --- a/Sources/SuperwallKit/Superwall.swift +++ b/Sources/SuperwallKit/Superwall.swift @@ -2,77 +2,90 @@ import Foundation import StoreKit import Combine -/// The primary class for integrating Superwall into your application. It provides access to all its featured via static functions and variables. +/// The primary class for integrating Superwall into your application. After configuring via +/// ``configure(apiKey:delegate:options:)-65jyx``, It provides access to +/// all its featured via instance functions and variables. @objcMembers public final class Superwall: NSObject, ObservableObject { // MARK: - Public Properties /// The optional purchasing delegate of the Superwall instance. Set this in - /// ``configure(apiKey:delegate:purchasingDelegate:options:)-3jysg`` + /// ``configure(apiKey:delegate:options:)-65jyx`` /// when you want to manually handle the purchasing logic within your app. - public static var delegate: SuperwallDelegate? { + public var delegate: SuperwallDelegate? { get { - return shared.dependencyContainer.delegateAdapter.swiftDelegate + return dependencyContainer.delegateAdapter.swiftDelegate } set { - shared.dependencyContainer.delegateAdapter.swiftDelegate = newValue - shared.dependencyContainer.storeKitManager.coordinator.didToggleDelegate() + dependencyContainer.delegateAdapter.swiftDelegate = newValue + dependencyContainer.storeKitManager.coordinator.didToggleDelegate() } } /// The optional purchasing delegate of the Superwall instance. Set this in - /// ``configure(apiKey:delegate:purchasingDelegate:options:)-3jysg`` + /// ``configure(apiKey:delegate:options:)-65jyx`` /// when you want to manually handle the purchasing logic within your app. @available(swift, obsoleted: 1.0) @objc(delegate) - public static var objcDelegate: SuperwallDelegateObjc? { + public var objcDelegate: SuperwallDelegateObjc? { get { - return shared.dependencyContainer.delegateAdapter.objcDelegate + return dependencyContainer.delegateAdapter.objcDelegate } set { - shared.dependencyContainer.delegateAdapter.objcDelegate = newValue - shared.dependencyContainer.storeKitManager.coordinator.didToggleDelegate() + dependencyContainer.delegateAdapter.objcDelegate = newValue + dependencyContainer.storeKitManager.coordinator.didToggleDelegate() } } - /// Properties stored about the user, set using ``SuperwallKit/Superwall/setUserAttributes(_:)``. - public static var userAttributes: [String: Any] { - return shared.dependencyContainer.identityManager.userAttributes + /// Specifies the detail of the logs returned from the SDK to the console. + public var logLevel: LogLevel? { + get { + return options.logging.level + } + set { + options.logging.level = newValue + } + } + + /// Properties stored about the user, set using ``setUserAttributes(_:)``. + public var userAttributes: [String: Any] { + return dependencyContainer.identityManager.userAttributes } /// The presented paywall view controller. @MainActor - public static var presentedViewController: UIViewController? { - return shared.dependencyContainer.paywallManager.presentedViewController + public var presentedViewController: UIViewController? { + return dependencyContainer.paywallManager.presentedViewController } - /// A convenience variable to access and change the paywall options that you passed to ``SuperwallKit/Superwall/configure(apiKey:delegate:options:)-65jyx``. - public static var options: SuperwallOptions { - return shared.dependencyContainer.configManager.options + /// A convenience variable to access and change the paywall options that you passed + /// to ``configure(apiKey:delegate:options:)-65jyx``. + public var options: SuperwallOptions { + return dependencyContainer.configManager.options } - /// The ``PaywallInfo`` object of the most recentl```y presented view controller. + /// The ``PaywallInfo`` object of the most recently presented view controller. @MainActor - public static var latestPaywallInfo: PaywallInfo? { - let presentedPaywallInfo = shared.dependencyContainer.paywallManager.presentedViewController?.paywallInfo - return presentedPaywallInfo ?? shared.presentationItems.paywallInfo + public var latestPaywallInfo: PaywallInfo? { + let presentedPaywallInfo = dependencyContainer.paywallManager.presentedViewController?.paywallInfo + return presentedPaywallInfo ?? presentationItems.paywallInfo } /// The current user's id. /// - /// If you haven't called ``SuperwallKit/Superwall/logIn(userId:)`` or ``SuperwallKit/Superwall/createAccount(userId:)``, + /// If you haven't called ``logIn(userId:)`` or ``createAccount(userId:)``, /// this value will return an anonymous user id which is cached to disk - public static var userId: String { - return shared.dependencyContainer.identityManager.userId + public var userId: String { + return dependencyContainer.identityManager.userId } /// Indicates whether the user is logged in to Superwall. /// - /// If you have previously called ``SuperwallKit/Superwall/logIn(userId:)`` or - /// ``SuperwallKit/Superwall/createAccount(userId:)``, this will return true. + /// If you have previously called ``logIn(userId:)`` or + /// ``createAccount(userId:)``, this will return true. /// /// - Returns: A boolean indicating whether the user is logged in or not. - public static var isLoggedIn: Bool { - return shared.dependencyContainer.identityManager.isLoggedIn + public var isLoggedIn: Bool { + return dependencyContainer.identityManager.isLoggedIn } /// A published property that indicates whether the device has any active subscriptions. @@ -95,19 +108,15 @@ public final class Superwall: NSObject, ObservableObject { /// ``configure(apiKey:delegate:options:)-65jyx``. /// /// If you're using Combine or SwiftUI, you can subscribe or bind to this to get - /// notified whenever the user's subscription status changes. + /// notified when configuration has completed. @Published public var isConfigured = false /// The configured shared instance of ``Superwall``. /// - /// - Warning: This method will crash with `fatalError` if ``Superwall`` has - /// not been initialized through ``configure(apiKey:delegate:options:)-65jyx``. - /// If there's a chance that may have not happened yet, you can use - /// ``isConfigured`` to check if it's safe to call. - /// ### Related symbols - /// - ``isConfigured`` - @objc(sharedSuperwall) + /// - Warning: You must call ``configure(apiKey:delegate:options:)-65jyx`` + /// to initialize ``Superwall`` before using this. + @objc(sharedInstance) public static var shared: Superwall { guard let superwall = superwall else { #if DEBUG @@ -115,7 +124,9 @@ public final class Superwall: NSObject, ObservableObject { // This avoids lots of irrelevent error messages printed to console about Superwall not // being configured, which slows down the tests. if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil { - return Superwall() + let superwall = Superwall() + self.superwall = superwall + return superwall } #endif Logger.debug( @@ -146,31 +157,29 @@ public final class Superwall: NSObject, ObservableObject { return paywallViewController != nil } - // swiftlint:disable implicitly_unwrapped_optional /// Handles all dependencies. - var dependencyContainer: DependencyContainer! - // swiftlint:enable implicitly_unwrapped_optional + let dependencyContainer: DependencyContainer // MARK: - Private Functions - private override init() { - dependencyContainer = DependencyContainer(apiKey: "") + init(dependencyContainer: DependencyContainer = DependencyContainer()) { + self.dependencyContainer = dependencyContainer + super.init() } - private init( + private convenience init( apiKey: String, swiftDelegate: SuperwallDelegate? = nil, objcDelegate: SuperwallDelegateObjc? = nil, options: SuperwallOptions? = nil ) { - dependencyContainer = DependencyContainer( - apiKey: apiKey, + let dependencyContainer = DependencyContainer( swiftDelegate: swiftDelegate, objcDelegate: objcDelegate, options: options ) - hasActiveSubscription = dependencyContainer.storage.get(SubscriptionStatus.self) ?? false + self.init(dependencyContainer: dependencyContainer) - super.init() + hasActiveSubscription = dependencyContainer.storage.get(SubscriptionStatus.self) ?? false listenForConfig() @@ -205,11 +214,15 @@ public final class Superwall: NSObject, ObservableObject { // MARK: - Configuration /// Configures a shared instance of ``Superwall`` for use throughout your app. /// - /// Call this as soon as your app finishes launching in `application(_:didFinishLaunchingWithOptions:)`. Check out our article for a tutorial on how to configure the SDK. + /// Call this as soon as your app finishes launching in `application(_:didFinishLaunchingWithOptions:)`. + /// Check out our article for a tutorial on how to configure the SDK. /// - Parameters: - /// - apiKey: Your Public API Key that you can get from the Superwall dashboard settings. If you don't have an account, you can [sign up for free](https://superwall.com/sign-up). - /// - delegate: An optional class that conforms to ``SuperwallDelegate``. The delegate methods receive callbacks from the SDK in response to certain events on the paywall. - /// - options: A ``SuperwallOptions`` object which allows you to customise the appearance and behavior of the paywall. + /// - apiKey: Your Public API Key that you can get from the Superwall dashboard settings. If you don't have + /// an account, you can [sign up for free](https://superwall.com/sign-up). + /// - delegate: An optional class that conforms to ``SuperwallDelegate``. The delegate methods receive + /// callbacks from the SDK in response to certain events on the paywall. + /// - options: A ``SuperwallOptions`` object which allows you to customise the appearance and behavior + /// of the paywall. /// - Returns: The newly configured ``Superwall`` instance. @discardableResult public static func configure( @@ -272,10 +285,10 @@ public final class Superwall: NSObject, ObservableObject { /// /// To use this, first set ``PaywallOptions/shouldPreload`` to `false` when configuring the SDK. Then call this function when you would like preloading to begin. /// - /// Note: This will not reload any paywalls you've already preloaded via ``SuperwallKit/Superwall/preloadPaywalls(forEvents:)``. - public static func preloadAllPaywalls() { - Task.detached(priority: .userInitiated) { - await shared.dependencyContainer.configManager.preloadAllPaywalls() + /// Note: This will not reload any paywalls you've already preloaded via ``preloadPaywalls(forEvents:)``. + public func preloadAllPaywalls() { + Task { [weak self] in + await self?.dependencyContainer.configManager.preloadAllPaywalls() } } @@ -284,9 +297,9 @@ public final class Superwall: NSObject, ObservableObject { /// To use this, first set ``PaywallOptions/shouldPreload`` to `false` when configuring the SDK. Then call this function when you would like preloading to begin. /// /// Note: This will not reload any paywalls you've already preloaded. - public static func preloadPaywalls(forEvents eventNames: Set) { - Task.detached(priority: .userInitiated) { - await shared.dependencyContainer.configManager.preloadPaywalls(for: eventNames) + public func preloadPaywalls(forEvents eventNames: Set) { + Task { [weak self] in + await self?.dependencyContainer.configManager.preloadPaywalls(for: eventNames) } } @@ -294,12 +307,10 @@ public final class Superwall: NSObject, ObservableObject { /// Handles a deep link sent to your app to open a preview of your paywall. /// /// You can preview your paywall on-device before going live by utilizing paywall previews. This uses a deep link to render a preview of a paywall you've configured on the Superwall dashboard on your device. See for more. - public static func handleDeepLink(_ url: URL) { - Task.detached(priority: .utility) { - await track(InternalSuperwallEvent.DeepLink(url: url)) - } + public func handleDeepLink(_ url: URL) { Task { - await shared.dependencyContainer.debugManager.handle(deepLinkUrl: url) + await track(InternalSuperwallEvent.DeepLink(url: url)) + await dependencyContainer.debugManager.handle(deepLinkUrl: url) } } @@ -309,8 +320,8 @@ public final class Superwall: NSObject, ObservableObject { /// /// You can also preview your paywall in different locales using the in-app debugger. See for more. /// - Parameter localeIdentifier: The locale identifier for the language you would like to test. - public static func localizationOverride(localeIdentifier: String? = nil) { - shared.dependencyContainer.localizationManager.selectedLocale = localeIdentifier + public func localizationOverride(localeIdentifier: String? = nil) { + dependencyContainer.localizationManager.selectedLocale = localeIdentifier } } @@ -340,15 +351,15 @@ extension Superwall: PaywallViewControllerDelegate { from: paywallViewController ) case .initiateRestore: - await dependencyContainer.restorationHandler.tryToRestore(paywallViewController) + await dependencyContainer.restorationManager.tryToRestore(paywallViewController) case .openedURL(let url): - dependencyContainer.delegateAdapter?.willOpenURL(url: url) + dependencyContainer.delegateAdapter.willOpenURL(url: url) case .openedUrlInSafari(let url): - dependencyContainer.delegateAdapter?.willOpenURL(url: url) + dependencyContainer.delegateAdapter.willOpenURL(url: url) case .openedDeepLink(let url): - dependencyContainer.delegateAdapter?.willOpenDeepLink(url: url) + dependencyContainer.delegateAdapter.willOpenDeepLink(url: url) case .custom(let string): - dependencyContainer.delegateAdapter?.handleCustomPaywallAction(withName: string) + dependencyContainer.delegateAdapter.handleCustomPaywallAction(withName: string) } } } diff --git a/SuperwallKit.podspec b/SuperwallKit.podspec index 46235b083..0c071dbcc 100644 --- a/SuperwallKit.podspec +++ b/SuperwallKit.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "SuperwallKit" - s.version = "3.0.0-beta.1" + s.version = "3.0.0-beta.2" s.summary = "Superwall: In-App Paywalls Made Easy" s.description = "Paywall infrastructure for mobile apps :) we make things like editing your paywall and running price tests as easy as clicking a few buttons. superwall.com" diff --git a/Tests/SuperwallKitTests/Analytics/App Session/AppSessionManagerMock.swift b/Tests/SuperwallKitTests/Analytics/App Session/AppSessionManagerMock.swift index 8cff8e164..9acbecea5 100644 --- a/Tests/SuperwallKitTests/Analytics/App Session/AppSessionManagerMock.swift +++ b/Tests/SuperwallKitTests/Analytics/App Session/AppSessionManagerMock.swift @@ -8,6 +8,10 @@ import Foundation @testable import SuperwallKit +class AppManagerDelegateMock: AppManagerDelegate { + func didUpdateAppSession(_ appSession: AppSession) async {} +} + final class AppSessionManagerMock: AppSessionManager { var internalAppSession: AppSession override var appSession: AppSession { @@ -20,7 +24,7 @@ final class AppSessionManagerMock: AppSessionManager { storage: Storage ) { internalAppSession = appSession - super.init(configManager: configManager, storage: storage) + super.init(configManager: configManager, storage: storage, delegate: AppManagerDelegateMock()) } override func listenForAppSessionTimeout() { diff --git a/Tests/SuperwallKitTests/Analytics/App Session/AppSessionManagerTests.swift b/Tests/SuperwallKitTests/Analytics/App Session/AppSessionManagerTests.swift index 67e44bade..4700f2ed7 100644 --- a/Tests/SuperwallKitTests/Analytics/App Session/AppSessionManagerTests.swift +++ b/Tests/SuperwallKitTests/Analytics/App Session/AppSessionManagerTests.swift @@ -10,17 +10,21 @@ import XCTest @testable import SuperwallKit class AppSessionManagerTests: XCTestCase { - var appSessionManager: AppSessionManager! - - override func setUp() async throws { - let dependencyContainer = DependencyContainer(apiKey: "abc") + lazy var dependencyContainer: DependencyContainer = { + let dependencyContainer = DependencyContainer() appSessionManager = AppSessionManager( configManager: dependencyContainer.configManager, - storage: dependencyContainer.storage + storage: dependencyContainer.storage, + delegate: delegate ) - - appSessionManager.postInit(sessionEventsManager: dependencyContainer.sessionEventsManager) dependencyContainer.appSessionManager = appSessionManager + return dependencyContainer + }() + var appSessionManager: AppSessionManager! + let delegate = AppManagerDelegateMock() + + override func setUp() { + _ = dependencyContainer } func testAppWillResignActive() async { diff --git a/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackingLogicTests.swift b/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackingLogicTests.swift index aecfbf010..3cdb1b43e 100644 --- a/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackingLogicTests.swift +++ b/Tests/SuperwallKitTests/Analytics/Internal Tracking/TrackingLogicTests.swift @@ -338,9 +338,8 @@ final class TrackingLogicTests: XCTestCase { // MARK: - didStartNewSession func testDidStartNewSession_canTriggerPaywall_paywallAlreadyPresented() { - let dependencyContainer = DependencyContainer(apiKey: "") let outcome = TrackingLogic.canTriggerPaywall( - InternalSuperwallEvent.AppInstall(deviceHelper: dependencyContainer.deviceHelper), + InternalSuperwallEvent.AppInstall(appInstalledAtString: ""), triggers: Set(["app_install"]), isPaywallPresented: true ) @@ -348,9 +347,8 @@ final class TrackingLogicTests: XCTestCase { } func testDidStartNewSession_canTriggerPaywall_isntTrigger() { - let dependencyContainer = DependencyContainer(apiKey: "") let outcome = TrackingLogic.canTriggerPaywall( - InternalSuperwallEvent.AppInstall(deviceHelper: dependencyContainer.deviceHelper), + InternalSuperwallEvent.AppInstall(appInstalledAtString: ""), triggers: [], isPaywallPresented: false ) @@ -358,9 +356,8 @@ final class TrackingLogicTests: XCTestCase { } func testDidStartNewSession_canTriggerPaywall_isAllowedInternalEvent() { - let dependencyContainer = DependencyContainer(apiKey: "") let outcome = TrackingLogic.canTriggerPaywall( - InternalSuperwallEvent.AppInstall(deviceHelper: dependencyContainer.deviceHelper), + InternalSuperwallEvent.AppInstall(appInstalledAtString: ""), triggers: ["app_install"], isPaywallPresented: false ) @@ -368,7 +365,6 @@ final class TrackingLogicTests: XCTestCase { } func testDidStartNewSession_canTriggerPaywall_isNotInternalEvent() { - let dependencyContainer = DependencyContainer(apiKey: "") let outcome = TrackingLogic.canTriggerPaywall( UserInitiatedEvent.Track(rawName: "random_event", canImplicitlyTriggerPaywall: true), triggers: ["random_event"], diff --git a/Tests/SuperwallKitTests/Analytics/Session Events/MockSessionEventsQueue.swift b/Tests/SuperwallKitTests/Analytics/Session Events/MockSessionEventsQueue.swift index b99324952..311e267de 100644 --- a/Tests/SuperwallKitTests/Analytics/Session Events/MockSessionEventsQueue.swift +++ b/Tests/SuperwallKitTests/Analytics/Session Events/MockSessionEventsQueue.swift @@ -13,7 +13,6 @@ actor MockSessionEventsQueue: SessionEnqueuable { var transactions: [StoreTransaction] = [] func enqueue(_ triggerSession: TriggerSession) { - debugPrint("trigger sesss", triggerSession) triggerSessions.append(triggerSession) } diff --git a/Tests/SuperwallKitTests/Analytics/Session Events/SessionEventsDelegateMock.swift b/Tests/SuperwallKitTests/Analytics/Session Events/SessionEventsDelegateMock.swift index 408cb6a3c..4175bbc7e 100644 --- a/Tests/SuperwallKitTests/Analytics/Session Events/SessionEventsDelegateMock.swift +++ b/Tests/SuperwallKitTests/Analytics/Session Events/SessionEventsDelegateMock.swift @@ -10,7 +10,7 @@ import Foundation final class SessionEventsDelegateMock: SessionEventsDelegate { var queue: SessionEnqueuable - var triggerSession: TriggerSessionManager! + var triggerSession: TriggerSessionManager init( queue: SessionEnqueuable, diff --git a/Tests/SuperwallKitTests/Analytics/Session Events/SessionEventsManagerTests.swift b/Tests/SuperwallKitTests/Analytics/Session Events/SessionEventsManagerTests.swift index 583711062..9d24118f3 100644 --- a/Tests/SuperwallKitTests/Analytics/Session Events/SessionEventsManagerTests.swift +++ b/Tests/SuperwallKitTests/Analytics/Session Events/SessionEventsManagerTests.swift @@ -17,7 +17,7 @@ final class SessionEventsManagerTests: XCTestCase { internalCachedTriggerSessions: [], internalCachedTransactions: [] ) - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let network = NetworkMock(factory: dependencyContainer) _ = SessionEventsManager( queue: SessionEventsQueue( @@ -31,8 +31,9 @@ final class SessionEventsManagerTests: XCTestCase { factory: dependencyContainer ) - let twoHundredMilliseconds = UInt64(200_000_000) - try? await Task.sleep(nanoseconds: twoHundredMilliseconds) + let milliseconds = 200 + let nanoseconds = UInt64(milliseconds * 1_000_000) + try? await Task.sleep(nanoseconds: nanoseconds) XCTAssertNil(network.sentSessionEvents) XCTAssertFalse(storage.didClearCachedSessionEvents) @@ -40,8 +41,8 @@ final class SessionEventsManagerTests: XCTestCase { func testPostCachedSessionEvents_triggerSessionsOnly() async { let storage = StorageMock(internalCachedTriggerSessions: [.stub()]) - let dependencyContainer = DependencyContainer(apiKey: "") - let configManager = dependencyContainer.configManager! + let dependencyContainer = DependencyContainer() + let configManager = dependencyContainer.configManager configManager.config = .stub() let network = NetworkMock(factory: dependencyContainer) @@ -57,8 +58,9 @@ final class SessionEventsManagerTests: XCTestCase { factory: dependencyContainer ) - let twoHundredMilliseconds = UInt64(200_000_000) - try? await Task.sleep(nanoseconds: twoHundredMilliseconds) + let milliseconds = 200 + let nanoseconds = UInt64(milliseconds * 1_000_000) + try? await Task.sleep(nanoseconds: nanoseconds) XCTAssertTrue(network.sentSessionEvents!.transactions.isEmpty) XCTAssertFalse(network.sentSessionEvents!.triggerSessions.isEmpty) @@ -70,8 +72,8 @@ final class SessionEventsManagerTests: XCTestCase { internalCachedTriggerSessions: [.stub()], internalCachedTransactions: [.stub()] ) - let dependencyContainer = DependencyContainer(apiKey: "") - let configManager = dependencyContainer.configManager! + let dependencyContainer = DependencyContainer() + let configManager = dependencyContainer.configManager configManager.config = .stub() let network = NetworkMock(factory: dependencyContainer) @@ -87,8 +89,9 @@ final class SessionEventsManagerTests: XCTestCase { factory: dependencyContainer ) - let twoHundredMilliseconds = UInt64(200_000_000) - try? await Task.sleep(nanoseconds: twoHundredMilliseconds) + let milliseconds = 200 + let nanoseconds = UInt64(milliseconds * 1_000_000) + try? await Task.sleep(nanoseconds: nanoseconds) XCTAssertFalse(network.sentSessionEvents!.transactions.isEmpty) XCTAssertFalse(network.sentSessionEvents!.triggerSessions.isEmpty) @@ -100,8 +103,8 @@ final class SessionEventsManagerTests: XCTestCase { internalCachedTriggerSessions: [], internalCachedTransactions: [.stub()] ) - let dependencyContainer = DependencyContainer(apiKey: "") - let configManager = dependencyContainer.configManager! + let dependencyContainer = DependencyContainer() + let configManager = dependencyContainer.configManager configManager.config = .stub() let network = NetworkMock(factory: dependencyContainer) @@ -117,8 +120,9 @@ final class SessionEventsManagerTests: XCTestCase { factory: dependencyContainer ) - let twoHundredMilliseconds = UInt64(200_000_000) - try? await Task.sleep(nanoseconds: twoHundredMilliseconds) + let milliseconds = 200 + let nanoseconds = UInt64(milliseconds * 1_000_000) + try? await Task.sleep(nanoseconds: nanoseconds) XCTAssertFalse(network.sentSessionEvents!.transactions.isEmpty) XCTAssertTrue(network.sentSessionEvents!.triggerSessions.isEmpty) diff --git a/Tests/SuperwallKitTests/Analytics/Trigger Session Manager/TriggerSessionManagerLogicTests.swift b/Tests/SuperwallKitTests/Analytics/Trigger Session Manager/TriggerSessionManagerLogicTests.swift index 1a96a30b7..c35d1c88b 100644 --- a/Tests/SuperwallKitTests/Analytics/Trigger Session Manager/TriggerSessionManagerLogicTests.swift +++ b/Tests/SuperwallKitTests/Analytics/Trigger Session Manager/TriggerSessionManagerLogicTests.swift @@ -47,7 +47,7 @@ final class TriggerSessionManagerLogicTests: XCTestCase { parameters: [:], createdAt: Date() ) - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let viewController = dependencyContainer.makeDebugViewController(withDatabaseId: nil) let outcome = TriggerSessionManagerLogic.outcome( @@ -166,7 +166,7 @@ final class TriggerSessionManagerLogicTests: XCTestCase { createdAt: Date() ) - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let viewController = dependencyContainer.makeDebugViewController(withDatabaseId: nil) let outcome = TriggerSessionManagerLogic.outcome( @@ -209,7 +209,7 @@ final class TriggerSessionManagerLogicTests: XCTestCase { .setting(\.id, to: eventId) .setting(\.createdAt, to: eventCreatedAt) - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let viewController = dependencyContainer.makeDebugViewController(withDatabaseId: nil) let presentationInfo = PresentationInfo.explicitTrigger(event) @@ -235,7 +235,7 @@ final class TriggerSessionManagerLogicTests: XCTestCase { func testIdentifierPaywall_noPaywallResponse() { let eventName = "manual_present" - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let viewController = dependencyContainer.makeDebugViewController(withDatabaseId: nil) let outcome = TriggerSessionManagerLogic.outcome( diff --git a/Tests/SuperwallKitTests/Analytics/Trigger Session Manager/TriggerSessionManagerTests.swift b/Tests/SuperwallKitTests/Analytics/Trigger Session Manager/TriggerSessionManagerTests.swift index bc12be903..fea1cefc9 100644 --- a/Tests/SuperwallKitTests/Analytics/Trigger Session Manager/TriggerSessionManagerTests.swift +++ b/Tests/SuperwallKitTests/Analytics/Trigger Session Manager/TriggerSessionManagerTests.swift @@ -16,7 +16,7 @@ final class TriggerSessionManagerTests: XCTestCase { var dependencyContainer: DependencyContainer! override func setUp() { - dependencyContainer = DependencyContainer(apiKey: "") + dependencyContainer = DependencyContainer() queue = MockSessionEventsQueue() sessionEventsDelegate = SessionEventsDelegateMock( @@ -543,7 +543,6 @@ final class TriggerSessionManagerTests: XCTestCase { await queue.removeAllTriggerSessions() let triggerSessions21 = await queue.triggerSessions - print("Anything?", triggerSessions21) // When await sessionManager.trackProductsLoad( diff --git a/Tests/SuperwallKitTests/Config/Assignments/AssignmentLogicTests.swift b/Tests/SuperwallKitTests/Config/Assignments/AssignmentLogicTests.swift index 25fb6309a..5089b10bd 100644 --- a/Tests/SuperwallKitTests/Config/Assignments/AssignmentLogicTests.swift +++ b/Tests/SuperwallKitTests/Config/Assignments/AssignmentLogicTests.swift @@ -37,7 +37,7 @@ class AssignmentLogicTests: XCTestCase { ) let triggers = [eventName: trigger] - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let variant = variantOption.toVariant() dependencyContainer.configManager.unconfirmedAssignments = [ rawExperiment.id: variant @@ -45,11 +45,10 @@ class AssignmentLogicTests: XCTestCase { let storage = StorageMock() // MARK: When - let assignmentLogic = AssignmentLogic( + let assignmentLogic = RuleLogic( configManager: dependencyContainer.configManager, storage: storage, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper + factory: dependencyContainer ) let outcome = assignmentLogic.evaluateRules( forEvent: eventData, @@ -101,17 +100,16 @@ class AssignmentLogicTests: XCTestCase { ) let triggers = [eventName: trigger] - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let variant = variantOption.toVariant() dependencyContainer.configManager.unconfirmedAssignments = [ rawExperiment.id: variant ] let storage = StorageMock() - let assignmentLogic = AssignmentLogic( + let assignmentLogic = RuleLogic( configManager: dependencyContainer.configManager, storage: storage, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper + factory: dependencyContainer ) // MARK: When @@ -166,14 +164,13 @@ class AssignmentLogicTests: XCTestCase { ) let triggers = [eventName: trigger] - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let variant = variantOption.toVariant() let storage = StorageMock(confirmedAssignments: [rawExperiment.id: variant]) - let assignmentLogic = AssignmentLogic( + let assignmentLogic = RuleLogic( configManager: dependencyContainer.configManager, storage: storage, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper + factory: dependencyContainer ) // MARK: When @@ -224,7 +221,7 @@ class AssignmentLogicTests: XCTestCase { ) let triggers = [eventName: trigger] - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let variant = variantOption.toVariant() let variant2 = variantOption .setting(\.paywallId, to: "123") @@ -233,11 +230,10 @@ class AssignmentLogicTests: XCTestCase { dependencyContainer.configManager.unconfirmedAssignments = [ rawExperiment.id: variant2 ] - let assignmentLogic = AssignmentLogic( + let assignmentLogic = RuleLogic( configManager: dependencyContainer.configManager, storage: storage, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper + factory: dependencyContainer ) // MARK: When @@ -288,17 +284,16 @@ class AssignmentLogicTests: XCTestCase { ) let triggers = [eventName: trigger] - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let storage = StorageMock() let variant = variantOption.toVariant() dependencyContainer.configManager.unconfirmedAssignments = [ rawExperiment.id: variant ] - let assignmentLogic = AssignmentLogic( + let assignmentLogic = RuleLogic( configManager: dependencyContainer.configManager, storage: storage, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper + factory: dependencyContainer ) // MARK: When @@ -344,17 +339,16 @@ class AssignmentLogicTests: XCTestCase { ) let triggers = [eventName: trigger] - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let storage = StorageMock() let variant = variantOption.toVariant() dependencyContainer.configManager.unconfirmedAssignments = [ rawExperiment.id: variant ] - let assignmentLogic = AssignmentLogic( + let assignmentLogic = RuleLogic( configManager: dependencyContainer.configManager, storage: storage, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper + factory: dependencyContainer ) // MARK: When diff --git a/Tests/SuperwallKitTests/Config/Assignments/Expression Evaluator/ExpressionEvaluatorLogicTests.swift b/Tests/SuperwallKitTests/Config/Assignments/Expression Evaluator/ExpressionEvaluatorLogicTests.swift index 1b27e6d5a..c2b91ebbe 100644 --- a/Tests/SuperwallKitTests/Config/Assignments/Expression Evaluator/ExpressionEvaluatorLogicTests.swift +++ b/Tests/SuperwallKitTests/Config/Assignments/Expression Evaluator/ExpressionEvaluatorLogicTests.swift @@ -12,12 +12,11 @@ import XCTest @available(iOS 14.0, *) final class ExpressionEvaluatorLogicTests: XCTestCase { func testShouldFire_noMatch() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let storage = StorageMock() let evaluator = ExpressionEvaluator( storage: storage, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper + factory: dependencyContainer ) let shouldFire = evaluator.shouldFire( forOccurrence: .stub(), @@ -28,12 +27,11 @@ final class ExpressionEvaluatorLogicTests: XCTestCase { } func testShouldFire_noOccurrenceRule() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let storage = StorageMock() let evaluator = ExpressionEvaluator( storage: storage, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper + factory: dependencyContainer ) let shouldFire = evaluator.shouldFire( forOccurrence: nil, @@ -44,13 +42,12 @@ final class ExpressionEvaluatorLogicTests: XCTestCase { } func testShouldFire_shouldntFire_maxCountGTCount() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let coreDataManagerMock = CoreDataManagerFakeDataMock(internalOccurrenceCount: 1) let storage = StorageMock(coreDataManager: coreDataManagerMock) let evaluator = ExpressionEvaluator( storage: storage, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper + factory: dependencyContainer ) let shouldFire = evaluator.shouldFire( forOccurrence: .stub() @@ -62,13 +59,12 @@ final class ExpressionEvaluatorLogicTests: XCTestCase { } func testShouldFire_shouldFire_maxCountEqualToCount() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let coreDataManagerMock = CoreDataManagerFakeDataMock(internalOccurrenceCount: 0) let storage = StorageMock(coreDataManager: coreDataManagerMock) let evaluator = ExpressionEvaluator( storage: storage, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper + factory: dependencyContainer ) let shouldFire = evaluator.shouldFire( forOccurrence: .stub() @@ -80,13 +76,12 @@ final class ExpressionEvaluatorLogicTests: XCTestCase { } func testShouldFire_shouldFire_maxCountLtCount() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let coreDataManagerMock = CoreDataManagerFakeDataMock(internalOccurrenceCount: 1) let storage = StorageMock(coreDataManager: coreDataManagerMock) let evaluator = ExpressionEvaluator( storage: storage, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper + factory: dependencyContainer ) let shouldFire = evaluator.shouldFire( forOccurrence: .stub() diff --git a/Tests/SuperwallKitTests/Config/Assignments/Expression Evaluator/ExpressionEvaluatorTests.swift b/Tests/SuperwallKitTests/Config/Assignments/Expression Evaluator/ExpressionEvaluatorTests.swift index 390dab499..10cfd0ff0 100644 --- a/Tests/SuperwallKitTests/Config/Assignments/Expression Evaluator/ExpressionEvaluatorTests.swift +++ b/Tests/SuperwallKitTests/Config/Assignments/Expression Evaluator/ExpressionEvaluatorTests.swift @@ -12,12 +12,11 @@ import XCTest final class ExpressionEvaluatorTests: XCTestCase { func testExpressionMatchesAll() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() dependencyContainer.storage.reset() let evaluator = ExpressionEvaluator( storage: dependencyContainer.storage, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper + factory: dependencyContainer ) let result = evaluator.evaluateExpression( fromRule: .stub() @@ -32,12 +31,11 @@ final class ExpressionEvaluatorTests: XCTestCase { // MARK: - Expression func testExpressionEvaluator_expressionTrue() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() dependencyContainer.storage.reset() let evaluator = ExpressionEvaluator( storage: dependencyContainer.storage, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper + factory: dependencyContainer ) dependencyContainer.identityManager.userAttributes = ["a": "b"] let result = evaluator.evaluateExpression( @@ -50,12 +48,11 @@ final class ExpressionEvaluatorTests: XCTestCase { } func testExpressionEvaluator_expressionParams() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() dependencyContainer.storage.reset() let evaluator = ExpressionEvaluator( storage: dependencyContainer.storage, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper + factory: dependencyContainer ) dependencyContainer.identityManager.userAttributes = [:] let result = evaluator.evaluateExpression( @@ -68,12 +65,11 @@ final class ExpressionEvaluatorTests: XCTestCase { } func testExpressionEvaluator_expressionDeviceTrue() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() dependencyContainer.storage.reset() let evaluator = ExpressionEvaluator( storage: dependencyContainer.storage, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper + factory: dependencyContainer ) dependencyContainer.identityManager.userAttributes = [:] let result = evaluator.evaluateExpression( @@ -86,12 +82,11 @@ final class ExpressionEvaluatorTests: XCTestCase { } func testExpressionEvaluator_expressionDeviceFalse() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() dependencyContainer.storage.reset() let evaluator = ExpressionEvaluator( storage: dependencyContainer.storage, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper + factory: dependencyContainer ) dependencyContainer.identityManager.userAttributes = [:] let result = evaluator.evaluateExpression( @@ -104,12 +99,11 @@ final class ExpressionEvaluatorTests: XCTestCase { } func testExpressionEvaluator_expressionFalse() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() dependencyContainer.storage.reset() let evaluator = ExpressionEvaluator( storage: dependencyContainer.storage, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper + factory: dependencyContainer ) dependencyContainer.identityManager.userAttributes = [:] let result = evaluator.evaluateExpression( @@ -138,11 +132,10 @@ final class ExpressionEvaluatorTests: XCTestCase { // MARK: - ExpressionJS func testExpressionEvaluator_expressionJSTrue() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let evaluator = ExpressionEvaluator( storage: dependencyContainer.storage, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper + factory: dependencyContainer ) let result = evaluator.evaluateExpression( fromRule: .stub() @@ -154,11 +147,10 @@ final class ExpressionEvaluatorTests: XCTestCase { } func testExpressionEvaluator_expressionJSValues_true() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let evaluator = ExpressionEvaluator( storage: dependencyContainer.storage, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper + factory: dependencyContainer ) let result = evaluator.evaluateExpression( fromRule: .stub() @@ -170,11 +162,10 @@ final class ExpressionEvaluatorTests: XCTestCase { } func testExpressionEvaluator_expressionJSValues_false() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let evaluator = ExpressionEvaluator( storage: dependencyContainer.storage, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper + factory: dependencyContainer ) let result = evaluator.evaluateExpression( fromRule: .stub() @@ -186,11 +177,10 @@ final class ExpressionEvaluatorTests: XCTestCase { } func testExpressionEvaluator_expressionJSNumbers() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let evaluator = ExpressionEvaluator( storage: dependencyContainer.storage, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper + factory: dependencyContainer ) let result = evaluator.evaluateExpression( fromRule: .stub() @@ -216,11 +206,10 @@ final class ExpressionEvaluatorTests: XCTestCase { }*/ func testExpressionEvaluator_expressionJSEmpty() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let evaluator = ExpressionEvaluator( storage: dependencyContainer.storage, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper + factory: dependencyContainer ) let result = evaluator.evaluateExpression( fromRule: .stub() diff --git a/Tests/SuperwallKitTests/Config/ConfigLogicTests.swift b/Tests/SuperwallKitTests/Config/ConfigLogicTests.swift index 40ebad0e3..a4fb29c22 100644 --- a/Tests/SuperwallKitTests/Config/ConfigLogicTests.swift +++ b/Tests/SuperwallKitTests/Config/ConfigLogicTests.swift @@ -500,7 +500,7 @@ final class ConfigLogicTests: XCTestCase { func test_getStaticPaywall_deviceLocaleSpecifiedInConfig() { let locale = "en_GB" - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let response = ConfigLogic.getStaticPaywall( withId: "abc", config: .stub() diff --git a/Tests/SuperwallKitTests/Config/ConfigManagerTests.swift b/Tests/SuperwallKitTests/Config/ConfigManagerTests.swift index b85b6ddb0..d9ad2da9a 100644 --- a/Tests/SuperwallKitTests/Config/ConfigManagerTests.swift +++ b/Tests/SuperwallKitTests/Config/ConfigManagerTests.swift @@ -20,10 +20,11 @@ final class ConfigManagerTests: XCTestCase { experimentId: experimentId, variant: variant ) - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let network = NetworkMock(factory: dependencyContainer) let storage = StorageMock() let configManager = ConfigManager( + options: nil, storeKitManager: dependencyContainer.storeKitManager, storage: storage, network: network, @@ -32,8 +33,9 @@ final class ConfigManagerTests: XCTestCase { ) configManager.confirmAssignment(assignment) - let twoHundredMilliseconds = UInt64(200_000_000) - try? await Task.sleep(nanoseconds: twoHundredMilliseconds) + let milliseconds = 200 + let nanoseconds = UInt64(milliseconds * 1_000_000) + try? await Task.sleep(nanoseconds: nanoseconds) XCTAssertTrue(network.assignmentsConfirmed) XCTAssertEqual(storage.getConfirmedAssignments()[experimentId], variant) @@ -43,17 +45,17 @@ final class ConfigManagerTests: XCTestCase { // MARK: - Load Assignments func test_loadAssignments_noConfig() async { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let network = NetworkMock(factory: dependencyContainer) let storage = StorageMock() let configManager = ConfigManager( + options: nil, storeKitManager: dependencyContainer.storeKitManager, storage: storage, network: network, paywallManager: dependencyContainer.paywallManager, factory: dependencyContainer ) - configManager.config = nil await configManager.getAssignments() @@ -62,10 +64,11 @@ final class ConfigManagerTests: XCTestCase { } func test_loadAssignments_noTriggers() async { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let network = NetworkMock(factory: dependencyContainer) let storage = StorageMock() let configManager = ConfigManager( + options: nil, storeKitManager: dependencyContainer.storeKitManager, storage: storage, network: network, @@ -82,17 +85,17 @@ final class ConfigManagerTests: XCTestCase { } func test_loadAssignments_saveAssignmentsFromServer() async { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let network = NetworkMock(factory: dependencyContainer) let storage = StorageMock() let configManager = ConfigManager( + options: nil, storeKitManager: dependencyContainer.storeKitManager, storage: storage, network: network, paywallManager: dependencyContainer.paywallManager, factory: dependencyContainer ) - configManager.postInit(deviceHelper: dependencyContainer.deviceHelper) let variantId = "variantId" let experimentId = "experimentId" diff --git a/Tests/SuperwallKitTests/Dependencies/StoreKitCoordinatorFactoryMock.swift b/Tests/SuperwallKitTests/Dependencies/StoreKitCoordinatorFactoryMock.swift index 9d783daaa..f322a30e0 100644 --- a/Tests/SuperwallKitTests/Dependencies/StoreKitCoordinatorFactoryMock.swift +++ b/Tests/SuperwallKitTests/Dependencies/StoreKitCoordinatorFactoryMock.swift @@ -8,9 +8,13 @@ import Foundation @testable import SuperwallKit -struct StoreKitCoordinatorFactoryMock: StoreKitCoordinatorFactory { +final class StoreKitCoordinatorFactoryMock: StoreKitCoordinatorFactory { let coordinator: StoreKitCoordinator + init(coordinator: StoreKitCoordinator) { + self.coordinator = coordinator + } + func makeStoreKitCoordinator() -> StoreKitCoordinator { return coordinator } diff --git a/Tests/SuperwallKitTests/Models/Config/ConfigResponseTests.swift b/Tests/SuperwallKitTests/Models/Config/ConfigResponseTests.swift index 18541e9b9..9d9fdf33b 100644 --- a/Tests/SuperwallKitTests/Models/Config/ConfigResponseTests.swift +++ b/Tests/SuperwallKitTests/Models/Config/ConfigResponseTests.swift @@ -424,7 +424,6 @@ final class ConfigTypeTests: XCTestCase { Config.self, from: response.data(using: .utf8)! ) - print(parsedResponse) XCTAssertTrue(parsedResponse.featureFlags.enableSessionEvents) guard let trigger = parsedResponse.triggers.filter({ $0.eventName == "MyEvent" }).first diff --git a/Tests/SuperwallKitTests/Network/Custom URL Session/CustomURLSessionMock.swift b/Tests/SuperwallKitTests/Network/Custom URL Session/CustomURLSessionMock.swift index 0e7e1020c..59cd041a2 100644 --- a/Tests/SuperwallKitTests/Network/Custom URL Session/CustomURLSessionMock.swift +++ b/Tests/SuperwallKitTests/Network/Custom URL Session/CustomURLSessionMock.swift @@ -12,10 +12,9 @@ final class CustomURLSessionMock: CustomURLSession { var didRequest = false override func request( - _ endpoint: Endpoint, - isForDebugging: Bool = false + _ endpoint: Endpoint ) async throws -> Response where Response: Decodable { didRequest = true - return try await super.request(endpoint, isForDebugging: isForDebugging) + return try await super.request(endpoint) } } diff --git a/Tests/SuperwallKitTests/Network/NetworkMock.swift b/Tests/SuperwallKitTests/Network/NetworkMock.swift index 5d765ad3e..e5f62e466 100644 --- a/Tests/SuperwallKitTests/Network/NetworkMock.swift +++ b/Tests/SuperwallKitTests/Network/NetworkMock.swift @@ -20,7 +20,6 @@ final class NetworkMock: Network { sentSessionEvents = session } override func getConfig( - withRequestId requestId: String, injectedApplicationStatePublisher: (AnyPublisher)? = nil ) async throws -> Config { getConfigCalled = true diff --git a/Tests/SuperwallKitTests/Network/NetworkTests.swift b/Tests/SuperwallKitTests/Network/NetworkTests.swift index 61afb6ef6..f107a8dbf 100644 --- a/Tests/SuperwallKitTests/Network/NetworkTests.swift +++ b/Tests/SuperwallKitTests/Network/NetworkTests.swift @@ -18,19 +18,19 @@ final class NetworkTests: XCTestCase { completion: @escaping () -> Void ) async { let task = Task { - let dependencyContainer = DependencyContainer(apiKey: "abc") + let dependencyContainer = DependencyContainer() let network = Network(urlSession: urlSession, factory: dependencyContainer) let requestId = "abc" _ = try? await network.getConfig( - withRequestId: requestId, injectedApplicationStatePublisher: injectedApplicationStatePublisher ) completion() } - let twoHundredMilliseconds = UInt64(200_000_000) - try? await Task.sleep(nanoseconds: twoHundredMilliseconds) + let milliseconds = 200 + let nanoseconds = UInt64(milliseconds * 1_000_000) + try? await Task.sleep(nanoseconds: nanoseconds) task.cancel() } @@ -55,14 +55,13 @@ final class NetworkTests: XCTestCase { func test_config_inForeground() async { let urlSession = CustomURLSessionMock() - let dependencyContainer = DependencyContainer(apiKey: "abc") + let dependencyContainer = DependencyContainer() let network = Network(urlSession: urlSession, factory: dependencyContainer) let requestId = "abc" let publisher = Just(UIApplication.State.active) .eraseToAnyPublisher() _ = try? await network.getConfig( - withRequestId: requestId, injectedApplicationStatePublisher: publisher ) XCTAssertTrue(urlSession.didRequest) diff --git a/Tests/SuperwallKitTests/Paywall Manager/PaywallCacheTests.swift b/Tests/SuperwallKitTests/Paywall Manager/PaywallCacheTests.swift index f1ed62a09..8b9b6ed26 100644 --- a/Tests/SuperwallKitTests/Paywall Manager/PaywallCacheTests.swift +++ b/Tests/SuperwallKitTests/Paywall Manager/PaywallCacheTests.swift @@ -14,7 +14,7 @@ import XCTest class PaywallCacheTests: XCTestCase { func testSaveAndRetrievePaywall() throws { // Given - let dependencyContainer = DependencyContainer(apiKey: "abc") + let dependencyContainer = DependencyContainer() let locale = dependencyContainer.deviceHelper.locale let paywallCache = PaywallCache(deviceLocaleString: locale) let id = "myid" @@ -25,7 +25,7 @@ class PaywallCacheTests: XCTestCase { // When PaywallViewController.cache.insert(paywall) - let cachedPaywall = paywallCache.getPaywall(withKey: key) + let cachedPaywall = paywallCache.getPaywallViewController(key: key) // Then XCTAssertEqual(cachedPaywall, paywall) @@ -33,7 +33,7 @@ class PaywallCacheTests: XCTestCase { func testSaveAndRemovePaywall_withId() { // Given - let dependencyContainer = DependencyContainer(apiKey: "abc") + let dependencyContainer = DependencyContainer() let locale = dependencyContainer.deviceHelper.locale let paywallCache = PaywallCache(deviceLocaleString: locale) let id = "myid" @@ -44,23 +44,21 @@ class PaywallCacheTests: XCTestCase { // When PaywallViewController.cache.insert(paywall) - var cachedPaywall = paywallCache.getPaywall(withKey: key) + var cachedPaywall = paywallCache.getPaywallViewController(key: key) XCTAssertEqual(cachedPaywall, paywall) - paywallCache.removePaywall( - withIdentifier: id - ) + paywallCache.removePaywallViewController(identifier: id) // Then - cachedPaywall = paywallCache.getPaywall(withKey: key) + cachedPaywall = paywallCache.getPaywallViewController(key: key) XCTAssertNil(cachedPaywall) } func testSaveAndRemovePaywall_withVc() { // Given - let dependencyContainer = DependencyContainer(apiKey: "abc") + let dependencyContainer = DependencyContainer() let locale = dependencyContainer.deviceHelper.locale let paywallCache = PaywallCache(deviceLocaleString: locale) let paywallVc = dependencyContainer.makePaywallViewController(for: .stub()) @@ -73,21 +71,21 @@ class PaywallCacheTests: XCTestCase { locale: locale ) - var cachedPaywallVc = paywallCache.getPaywall(withKey: key) + var cachedPaywallVc = paywallCache.getPaywallViewController(key: key) XCTAssertEqual(cachedPaywallVc, paywallVc) - paywallCache.removePaywall(withViewController: paywallVc) + paywallCache.removePaywallViewController(paywallVc) // Then - cachedPaywallVc = paywallCache.getPaywall(withKey: key) + cachedPaywallVc = paywallCache.getPaywallViewController(key: key) XCTAssertNil(cachedPaywallVc) } func testClearCache() { // Given - let dependencyContainer = DependencyContainer(apiKey: "abc") + let dependencyContainer = DependencyContainer() let locale = dependencyContainer.deviceHelper.locale let paywallCache = PaywallCache(deviceLocaleString: locale) let paywallId1 = "id1" @@ -105,8 +103,8 @@ class PaywallCacheTests: XCTestCase { PaywallViewController.cache.insert(paywall1) PaywallViewController.cache.insert(paywall2) - let cachedPaywall1 = paywallCache.getPaywall(withKey: key1) - let cachedPaywall2 = paywallCache.getPaywall(withKey: key2) + let cachedPaywall1 = paywallCache.getPaywallViewController(key: key1) + let cachedPaywall2 = paywallCache.getPaywallViewController(key: key2) XCTAssertEqual(cachedPaywall1, paywall1) XCTAssertEqual(cachedPaywall2, paywall2) @@ -114,8 +112,8 @@ class PaywallCacheTests: XCTestCase { paywallCache.clearCache() // Then - let nilPaywall1 = paywallCache.getPaywall(withKey: key1) - let nilPaywall2 = paywallCache.getPaywall(withKey: key2) + let nilPaywall1 = paywallCache.getPaywallViewController(key: key1) + let nilPaywall2 = paywallCache.getPaywallViewController(key: key2) XCTAssertNil(nilPaywall1) XCTAssertNil(nilPaywall2) @@ -123,7 +121,7 @@ class PaywallCacheTests: XCTestCase { func testViewControllers() { // Given - let dependencyContainer = DependencyContainer(apiKey: "abc") + let dependencyContainer = DependencyContainer() let paywall1 = dependencyContainer.makePaywallViewController(for: .stub()) paywall1.cacheKey = "myid1" let paywall2 = dependencyContainer.makePaywallViewController(for: .stub()) diff --git a/Tests/SuperwallKitTests/Paywall Presentation/Get Track Result/Operators/CheckPaywallIsPresentableTests.swift b/Tests/SuperwallKitTests/Paywall Presentation/Get Track Result/Operators/CheckPaywallIsPresentableTests.swift index 32d379bd7..6032817cc 100644 --- a/Tests/SuperwallKitTests/Paywall Presentation/Get Track Result/Operators/CheckPaywallIsPresentableTests.swift +++ b/Tests/SuperwallKitTests/Paywall Presentation/Get Track Result/Operators/CheckPaywallIsPresentableTests.swift @@ -16,10 +16,10 @@ final class CheckPaywallIsPresentableTests: XCTestCase { func test_checkPaywallIsPresentable_userIsSubscribed() async { let expectation = expectation(description: "Did throw") - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let paywallVcPipelineOutput = await PaywallVcPipelineOutput( request: .stub() - .setting(\.injections.isUserSubscribed, to: true), + .setting(\.flags.isUserSubscribed, to: true), triggerResult: .paywall(.stub()), debugInfo: [:], paywallViewController: dependencyContainer.makePaywallViewController(for: .stub()), @@ -56,11 +56,11 @@ final class CheckPaywallIsPresentableTests: XCTestCase { func test_checkPaywallIsPresentable_userNotSubscribed() async { let expectation = expectation(description: "Did throw") - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let triggerResult: TriggerResult = .paywall(.stub()) let paywallVcPipelineOutput = await PaywallVcPipelineOutput( request: .stub() - .setting(\.injections.isUserSubscribed, to: false), + .setting(\.flags.isUserSubscribed, to: false), triggerResult: triggerResult, debugInfo: [:], paywallViewController: dependencyContainer.makePaywallViewController(for: .stub()), diff --git a/Tests/SuperwallKitTests/Paywall Presentation/Get Track Result/Operators/GetPaywallViewControllerNoChecksTests.swift b/Tests/SuperwallKitTests/Paywall Presentation/Get Track Result/Operators/GetPaywallViewControllerNoChecksTests.swift index 80009aa75..777ee1e52 100644 --- a/Tests/SuperwallKitTests/Paywall Presentation/Get Track Result/Operators/GetPaywallViewControllerNoChecksTests.swift +++ b/Tests/SuperwallKitTests/Paywall Presentation/Get Track Result/Operators/GetPaywallViewControllerNoChecksTests.swift @@ -14,7 +14,7 @@ final class GetPaywallVcNoChecksOperatorTests: XCTestCase { @MainActor func test_getPaywallViewController_error_userSubscribed() async { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let paywallManager = PaywallManagerMock( factory: dependencyContainer, paywallRequestManager: dependencyContainer.paywallRequestManager @@ -22,8 +22,8 @@ final class GetPaywallVcNoChecksOperatorTests: XCTestCase { paywallManager.getPaywallError = PresentationPipelineError.cancelled let request = PresentationRequest.stub() - .setting(\.injections.paywallManager, to: paywallManager) - .setting(\.injections.isUserSubscribed, to: false) + .setting(\.dependencyContainer.paywallManager, to: paywallManager) + .setting(\.flags.isUserSubscribed, to: false) let input = TriggerResultResponsePipelineOutput( request: request, @@ -61,7 +61,7 @@ final class GetPaywallVcNoChecksOperatorTests: XCTestCase { @MainActor func test_getPaywallViewController() async { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let paywallManager = PaywallManagerMock( factory: dependencyContainer, paywallRequestManager: dependencyContainer.paywallRequestManager @@ -69,8 +69,8 @@ final class GetPaywallVcNoChecksOperatorTests: XCTestCase { paywallManager.getPaywallVc = dependencyContainer.makePaywallViewController(for: .stub()) let request = PresentationRequest.stub() - .setting(\.injections.paywallManager, to: paywallManager) - .setting(\.injections.isUserSubscribed, to: false) + .setting(\.dependencyContainer.paywallManager, to: paywallManager) + .setting(\.flags.isUserSubscribed, to: false) let triggerResult: TriggerResult = .paywall(.stub()) let input = TriggerResultResponsePipelineOutput( diff --git a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/AwaitIdentityOperatorTests.swift b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/AwaitIdentityOperatorTests.swift index c95c0d929..74c397ab7 100644 --- a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/AwaitIdentityOperatorTests.swift +++ b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/AwaitIdentityOperatorTests.swift @@ -12,7 +12,7 @@ import Combine final class AwaitIdentityOperatorTests: XCTestCase { var cancellables: [AnyCancellable] = [] let identityManager: IdentityManager = { - let dependencyContainer = DependencyContainer(apiKey: "abc") + let dependencyContainer = DependencyContainer() return dependencyContainer.identityManager }() @@ -44,7 +44,7 @@ final class AwaitIdentityOperatorTests: XCTestCase { let expectation = expectation(description: "Got identity") let stub = PresentationRequest.stub() - .setting(\.injections.identityManager, to: identityManager) + .setting(\.dependencyContainer.identityManager, to: identityManager) CurrentValueSubject(stub) .setFailureType(to: Error.self) diff --git a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/CheckDebuggerPresentationOperatorTests.swift b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/CheckDebuggerPresentationOperatorTests.swift index bcf943c30..0ed059673 100644 --- a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/CheckDebuggerPresentationOperatorTests.swift +++ b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/CheckDebuggerPresentationOperatorTests.swift @@ -14,7 +14,7 @@ final class CheckDebuggerPresentationOperatorTests: XCTestCase { func test_checkDebuggerPresentation_debuggerNotLaunched() async { let request = PresentationRequest.stub() - .setting(\.injections.isDebuggerLaunched, to: false) + .setting(\.flags.isDebuggerLaunched, to: false) let debugInfo: [String: Any] = [:] @@ -44,10 +44,10 @@ final class CheckDebuggerPresentationOperatorTests: XCTestCase { } func test_checkDebuggerPresentation_debuggerLaunched_presentingOnDebugger() async { - let dependencyContainer = DependencyContainer(apiKey: "abc") + let dependencyContainer = DependencyContainer() let debugViewController = await dependencyContainer.makeDebugViewController(withDatabaseId: "abc") let request = PresentationRequest.stub() - .setting(\.injections.isDebuggerLaunched, to: true) + .setting(\.flags.isDebuggerLaunched, to: true) .setting(\.presentingViewController, to: debugViewController) let debugInfo: [String: Any] = [:] @@ -79,7 +79,7 @@ final class CheckDebuggerPresentationOperatorTests: XCTestCase { func test_checkDebuggerPresentation_debuggerLaunched_notPresentingOnDebugger() async { let request = PresentationRequest.stub() - .setting(\.injections.isDebuggerLaunched, to: true) + .setting(\.flags.isDebuggerLaunched, to: true) .setting(\.presentingViewController, to: nil) let debugInfo: [String: Any] = [:] diff --git a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/CheckPaywallPresentableOperatorTests.swift b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/CheckPaywallPresentableOperatorTests.swift index 44b44b31d..6f4eb308b 100644 --- a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/CheckPaywallPresentableOperatorTests.swift +++ b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/CheckPaywallPresentableOperatorTests.swift @@ -34,9 +34,9 @@ final class CheckPaywallPresentableOperatorTests: XCTestCase { } } .store(in: &cancellables) - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let request = PresentationRequest.stub() - .setting(\.injections.isUserSubscribed, to: true) + .setting(\.flags.isUserSubscribed, to: true) let input = PaywallVcPipelineOutput( request: request, @@ -100,16 +100,17 @@ final class CheckPaywallPresentableOperatorTests: XCTestCase { } .store(in: &cancellables) - let dependencyContainer = DependencyContainer(apiKey: "abc") + Superwall.shared.presentationItems.window = UIWindow() + + let dependencyContainer = DependencyContainer() let request = dependencyContainer.makePresentationRequest( .explicitTrigger(.stub()), isDebuggerLaunched: false, isUserSubscribed: false, isPaywallPresented: false ) - .setting(\.presentingViewController, to: nil) - .setting(\.injections.superwall.presentationItems.window, to: UIWindow()) - + .setting(\.presentingViewController, to: nil) + let input = PaywallVcPipelineOutput( request: request, triggerResult: .paywall(experiment), @@ -161,9 +162,9 @@ final class CheckPaywallPresentableOperatorTests: XCTestCase { let request = PresentationRequest.stub() .setting(\.presentingViewController, to: UIViewController()) - .setting(\.injections.isUserSubscribed, to: false) + .setting(\.flags.isUserSubscribed, to: false) - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let input = PaywallVcPipelineOutput( request: request, triggerResult: .paywall(experiment), diff --git a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/CheckUserSubscriptionOperatorTests.swift b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/CheckUserSubscriptionOperatorTests.swift index 943f99e18..1cc8a97ac 100644 --- a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/CheckUserSubscriptionOperatorTests.swift +++ b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/CheckUserSubscriptionOperatorTests.swift @@ -13,23 +13,12 @@ final class CheckUserSubscriptionOperatorTests: XCTestCase { var cancellables: [AnyCancellable] = [] func test_checkUserSubscription_notPaywall_userSubscribed() async { - let dependencyContainer = DependencyContainer(apiKey: "") - let request = PresentationRequest( - presentationInfo: .explicitTrigger(.stub()), - injections: .init( - configManager: dependencyContainer.configManager, - storage: dependencyContainer.storage, - sessionEventsManager: dependencyContainer.sessionEventsManager, - paywallManager: dependencyContainer.paywallManager, - storeKitManager: dependencyContainer.storeKitManager, - network: dependencyContainer.network, - debugManager: dependencyContainer.debugManager, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper, - isDebuggerLaunched: false, - isUserSubscribed: true, - isPaywallPresented: false - ) + let dependencyContainer = DependencyContainer() + let request = dependencyContainer.makePresentationRequest( + .explicitTrigger(.stub()), + isDebuggerLaunched: false, + isUserSubscribed: true, + isPaywallPresented: false ) let input = AssignmentPipelineOutput( @@ -80,7 +69,7 @@ final class CheckUserSubscriptionOperatorTests: XCTestCase { } func test_checkUserSubscription_paywall() async { - let dependencyContainer = DependencyContainer(apiKey: "abc") + let dependencyContainer = DependencyContainer() let request = dependencyContainer.makePresentationRequest( .explicitTrigger(.stub()), isDebuggerLaunched: false, @@ -121,23 +110,12 @@ final class CheckUserSubscriptionOperatorTests: XCTestCase { } func test_checkUserSubscription_notPaywall_userNotSubscribed() async { - let dependencyContainer = DependencyContainer(apiKey: "") - let request = PresentationRequest( - presentationInfo: .explicitTrigger(.stub()), - injections: .init( - configManager: dependencyContainer.configManager, - storage: dependencyContainer.storage, - sessionEventsManager: dependencyContainer.sessionEventsManager, - paywallManager: dependencyContainer.paywallManager, - storeKitManager: dependencyContainer.storeKitManager, - network: dependencyContainer.network, - debugManager: dependencyContainer.debugManager, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper, - isDebuggerLaunched: false, - isUserSubscribed: false, - isPaywallPresented: false - ) + let dependencyContainer = DependencyContainer() + let request = dependencyContainer.makePresentationRequest( + .explicitTrigger(.stub()), + isDebuggerLaunched: false, + isUserSubscribed: false, + isPaywallPresented: false ) let input = AssignmentPipelineOutput( diff --git a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/ConfirmHoldoutAssignment.swift b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/ConfirmHoldoutAssignment.swift index a894ca85d..0e13ac01a 100644 --- a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/ConfirmHoldoutAssignment.swift +++ b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/ConfirmHoldoutAssignment.swift @@ -13,30 +13,23 @@ final class ConfirmHoldoutAssignmentOperatorTests: XCTestCase { var cancellables: [AnyCancellable] = [] func test_confirmHoldoutAssignment_notHoldout() async { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() + let configManager = ConfigManagerMock( + options: nil, storeKitManager: dependencyContainer.storeKitManager, storage: dependencyContainer.storage, network: dependencyContainer.network, paywallManager: dependencyContainer.paywallManager, factory: dependencyContainer ) - let request = PresentationRequest( - presentationInfo: .explicitTrigger(.stub()), - injections: .init( - configManager: configManager, - storage: dependencyContainer.storage, - sessionEventsManager: dependencyContainer.sessionEventsManager, - paywallManager: dependencyContainer.paywallManager, - storeKitManager: dependencyContainer.storeKitManager, - network: dependencyContainer.network, - debugManager: dependencyContainer.debugManager, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper, - isDebuggerLaunched: false, - isUserSubscribed: false, - isPaywallPresented: false - ) + dependencyContainer.configManager = configManager + + let request = dependencyContainer.makePresentationRequest( + .explicitTrigger(.stub()), + isDebuggerLaunched: false, + isUserSubscribed: false, + isPaywallPresented: false ) let input = AssignmentPipelineOutput( request: request, @@ -63,31 +56,24 @@ final class ConfirmHoldoutAssignmentOperatorTests: XCTestCase { } func test_confirmHoldoutAssignment_holdout_noConfirmableAssignments() async { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let configManager = ConfigManagerMock( + options: nil, storeKitManager: dependencyContainer.storeKitManager, storage: dependencyContainer.storage, network: dependencyContainer.network, paywallManager: dependencyContainer.paywallManager, factory: dependencyContainer ) - let request = PresentationRequest( - presentationInfo: .explicitTrigger(.stub()), - injections: .init( - configManager: configManager, - storage: dependencyContainer.storage, - sessionEventsManager: dependencyContainer.sessionEventsManager, - paywallManager: dependencyContainer.paywallManager, - storeKitManager: dependencyContainer.storeKitManager, - network: dependencyContainer.network, - debugManager: dependencyContainer.debugManager, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper, - isDebuggerLaunched: false, - isUserSubscribed: false, - isPaywallPresented: false - ) + dependencyContainer.configManager = configManager + + let request = dependencyContainer.makePresentationRequest( + .explicitTrigger(.stub()), + isDebuggerLaunched: false, + isUserSubscribed: false, + isPaywallPresented: false ) + let input = AssignmentPipelineOutput( request: request, triggerResult: .holdout(.init(id: "", groupId: "", variant: .init(id: "", type: .treatment, paywallId: ""))), @@ -114,30 +100,21 @@ final class ConfirmHoldoutAssignmentOperatorTests: XCTestCase { } func test_confirmHoldoutAssignment_holdout_hasConfirmableAssignments() async { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let configManager = ConfigManagerMock( + options: nil, storeKitManager: dependencyContainer.storeKitManager, storage: dependencyContainer.storage, network: dependencyContainer.network, paywallManager: dependencyContainer.paywallManager, factory: dependencyContainer ) - let request = PresentationRequest( - presentationInfo: .explicitTrigger(.stub()), - injections: .init( - configManager: configManager, - storage: dependencyContainer.storage, - sessionEventsManager: dependencyContainer.sessionEventsManager, - paywallManager: dependencyContainer.paywallManager, - storeKitManager: dependencyContainer.storeKitManager, - network: dependencyContainer.network, - debugManager: dependencyContainer.debugManager, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper, - isDebuggerLaunched: false, - isUserSubscribed: false, - isPaywallPresented: false - ) + dependencyContainer.configManager = configManager + let request = dependencyContainer.makePresentationRequest( + .explicitTrigger(.stub()), + isDebuggerLaunched: false, + isUserSubscribed: false, + isPaywallPresented: false ) let input = AssignmentPipelineOutput( request: request, diff --git a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/ConfirmPaywallAssignmentOperatorTests.swift b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/ConfirmPaywallAssignmentOperatorTests.swift index fb4af02af..dbb8005a9 100644 --- a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/ConfirmPaywallAssignmentOperatorTests.swift +++ b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/ConfirmPaywallAssignmentOperatorTests.swift @@ -14,17 +14,18 @@ final class ConfirmPaywallAssignmentOperatorTests: XCTestCase { @MainActor func test_confirmPaywallAssignment_debuggerLaunched() async { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let configManager = ConfigManagerMock( + options: nil, storeKitManager: dependencyContainer.storeKitManager, storage: dependencyContainer.storage, network: dependencyContainer.network, paywallManager: dependencyContainer.paywallManager, factory: dependencyContainer ) + dependencyContainer.configManager = configManager let request = PresentationRequest.stub() - .setting(\.injections.isDebuggerLaunched, to: true) - .setting(\.injections.configManager, to: configManager) + .setting(\.flags.isDebuggerLaunched, to: true) let input = PresentablePipelineOutput( request: request, @@ -54,17 +55,19 @@ final class ConfirmPaywallAssignmentOperatorTests: XCTestCase { @MainActor func test_confirmPaywallAssignment_noAssignment() async { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let configManager = ConfigManagerMock( + options: nil, storeKitManager: dependencyContainer.storeKitManager, storage: dependencyContainer.storage, network: dependencyContainer.network, paywallManager: dependencyContainer.paywallManager, factory: dependencyContainer ) + dependencyContainer.configManager = configManager + let request = PresentationRequest.stub() - .setting(\.injections.isDebuggerLaunched, to: false) - .setting(\.injections.configManager, to: configManager) + .setting(\.flags.isDebuggerLaunched, to: false) let input = PresentablePipelineOutput( request: request, @@ -94,17 +97,22 @@ final class ConfirmPaywallAssignmentOperatorTests: XCTestCase { @MainActor func test_confirmPaywallAssignment_confirmAssignment() async { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let configManager = ConfigManagerMock( + options: nil, storeKitManager: dependencyContainer.storeKitManager, storage: dependencyContainer.storage, network: dependencyContainer.network, paywallManager: dependencyContainer.paywallManager, factory: dependencyContainer ) - let request = PresentationRequest.stub() - .setting(\.injections.isDebuggerLaunched, to: false) - .setting(\.injections.configManager, to: configManager) + dependencyContainer.configManager = configManager + + let request = dependencyContainer.makePresentationRequest( + .explicitTrigger(.stub()), + isDebuggerLaunched: false, + isPaywallPresented: false + ) let input = PresentablePipelineOutput( request: request, diff --git a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/EvaluateRulesOperatorTests.swift b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/EvaluateRulesOperatorTests.swift index 028e9dbf4..2ddd9c9e5 100644 --- a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/EvaluateRulesOperatorTests.swift +++ b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/EvaluateRulesOperatorTests.swift @@ -13,24 +13,14 @@ final class EvaluateRulesOperatorTests: XCTestCase { var cancellables: [AnyCancellable] = [] func test_evaluateRules_isDebugger() async { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let identifier = "abc" - let request = PresentationRequest( - presentationInfo: .fromIdentifier(identifier, freeTrialOverride: false), - injections: .init( - configManager: dependencyContainer.configManager, - storage: dependencyContainer.storage, - sessionEventsManager: dependencyContainer.sessionEventsManager, - paywallManager: dependencyContainer.paywallManager, - storeKitManager: dependencyContainer.storeKitManager, - network: dependencyContainer.network, - debugManager: dependencyContainer.debugManager, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper, - isDebuggerLaunched: true, - isUserSubscribed: false, - isPaywallPresented: false - ) + + let request = dependencyContainer.makePresentationRequest( + .fromIdentifier(identifier, freeTrialOverride: false), + isDebuggerLaunched: true, + isUserSubscribed: false, + isPaywallPresented: false ) let debugInfo: [String: Any] = [:] @@ -64,23 +54,12 @@ final class EvaluateRulesOperatorTests: XCTestCase { } func test_evaluateRules_isNotDebugger() async { - let dependencyContainer = DependencyContainer(apiKey: "") - let request = PresentationRequest( - presentationInfo: .explicitTrigger(.stub()), - injections: .init( - configManager: dependencyContainer.configManager, - storage: dependencyContainer.storage, - sessionEventsManager: dependencyContainer.sessionEventsManager, - paywallManager: dependencyContainer.paywallManager, - storeKitManager: dependencyContainer.storeKitManager, - network: dependencyContainer.network, - debugManager: dependencyContainer.debugManager, - identityManager: dependencyContainer.identityManager, - deviceHelper: dependencyContainer.deviceHelper, - isDebuggerLaunched: false, - isUserSubscribed: false, - isPaywallPresented: false - ) + let dependencyContainer = DependencyContainer() + let request = dependencyContainer.makePresentationRequest( + .explicitTrigger(.stub()), + isDebuggerLaunched: false, + isUserSubscribed: false, + isPaywallPresented: false ) let debugInfo: [String: Any] = [:] diff --git a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/GetPaywallVcOperator.swift b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/GetPaywallVcOperator.swift index e3ff047d3..f947bffc9 100644 --- a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/GetPaywallVcOperator.swift +++ b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/GetPaywallVcOperator.swift @@ -40,7 +40,7 @@ final class GetPaywallVcOperatorTests: XCTestCase { } .store(in: &cancellables) - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let paywallManager = PaywallManagerMock( factory: dependencyContainer, paywallRequestManager: dependencyContainer.paywallRequestManager @@ -48,8 +48,8 @@ final class GetPaywallVcOperatorTests: XCTestCase { paywallManager.getPaywallError = PresentationPipelineError.cancelled let request = PresentationRequest.stub() - .setting(\.injections.paywallManager, to: paywallManager) - .setting(\.injections.isUserSubscribed, to: true) + .setting(\.dependencyContainer.paywallManager, to: paywallManager) + .setting(\.flags.isUserSubscribed, to: true) let input = TriggerResultResponsePipelineOutput( request: request, @@ -113,7 +113,7 @@ final class GetPaywallVcOperatorTests: XCTestCase { } .store(in: &cancellables) - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let paywallManager = PaywallManagerMock( factory: dependencyContainer, paywallRequestManager: dependencyContainer.paywallRequestManager @@ -121,8 +121,8 @@ final class GetPaywallVcOperatorTests: XCTestCase { paywallManager.getPaywallError = PresentationPipelineError.cancelled let request = PresentationRequest.stub() - .setting(\.injections.paywallManager, to: paywallManager) - .setting(\.injections.isUserSubscribed, to: false) + .setting(\.dependencyContainer.paywallManager, to: paywallManager) + .setting(\.flags.isUserSubscribed, to: false) let input = TriggerResultResponsePipelineOutput( request: request, @@ -186,7 +186,7 @@ final class GetPaywallVcOperatorTests: XCTestCase { } .store(in: &cancellables) - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let paywallManager = PaywallManagerMock( factory: dependencyContainer, paywallRequestManager: dependencyContainer.paywallRequestManager @@ -194,8 +194,8 @@ final class GetPaywallVcOperatorTests: XCTestCase { paywallManager.getPaywallVc = dependencyContainer.makePaywallViewController(for: .stub()) let request = PresentationRequest.stub() - .setting(\.injections.paywallManager, to: paywallManager) - .setting(\.injections.isPaywallPresented, to: true) + .setting(\.dependencyContainer.paywallManager, to: paywallManager) + .setting(\.flags.isPaywallPresented, to: true) let input = TriggerResultResponsePipelineOutput( request: request, @@ -244,7 +244,7 @@ final class GetPaywallVcOperatorTests: XCTestCase { } .store(in: &cancellables) - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let paywallManager = PaywallManagerMock( factory: dependencyContainer, paywallRequestManager: dependencyContainer.paywallRequestManager @@ -252,8 +252,8 @@ final class GetPaywallVcOperatorTests: XCTestCase { paywallManager.getPaywallVc = dependencyContainer.makePaywallViewController(for: .stub()) let request = PresentationRequest.stub() - .setting(\.injections.paywallManager, to: paywallManager) - .setting(\.injections.isPaywallPresented, to: false) + .setting(\.dependencyContainer.paywallManager, to: paywallManager) + .setting(\.flags.isPaywallPresented, to: false) let input = TriggerResultResponsePipelineOutput( request: request, diff --git a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/HandleTriggerResultOperatorTests.swift b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/HandleTriggerResultOperatorTests.swift index 315414be6..5be56b38b 100644 --- a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/HandleTriggerResultOperatorTests.swift +++ b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/HandleTriggerResultOperatorTests.swift @@ -102,7 +102,7 @@ final class HandleTriggerResultOperatorTests: XCTestCase { ) .store(in: &cancellables) - wait(for: [expectation, stateExpectation], timeout: 0.1) + wait(for: [expectation, stateExpectation], timeout: 1) } func test_handleTriggerResult_noRuleMatch() { @@ -159,7 +159,7 @@ final class HandleTriggerResultOperatorTests: XCTestCase { ) .store(in: &cancellables) - wait(for: [expectation, stateExpectation], timeout: 0.1) + wait(for: [expectation, stateExpectation], timeout: 1) } func test_handleTriggerResult_eventNotFound() { @@ -278,6 +278,6 @@ final class HandleTriggerResultOperatorTests: XCTestCase { ) .store(in: &cancellables) - wait(for: [expectation, stateExpectation], timeout: 0.1) + wait(for: [expectation, stateExpectation], timeout: 1) } } diff --git a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/LogPresentationOperator.swift b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/LogPresentationOperator.swift deleted file mode 100644 index 13766679f..000000000 --- a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/LogPresentationOperator.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// File.swift -// -// -// Created by Yusuf Tör on 05/12/2022. -// - - -import XCTest -@testable import SuperwallKit -import Combine - -final class LogPresentationOperatorTests: XCTestCase { - var cancellables: [AnyCancellable] = [] - - func test_debugInfo() async { - let expectation = expectation(description: "Got identity") - - CurrentValueSubject(PresentationRequest - .stub() - .setting(\.injections.logger, to: LoggerMock.self)) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - .logPresentation("Test") - .eraseToAnyPublisher() - .sink( - receiveCompletion: { _ in }, - receiveValue: { value in - XCTAssertTrue(LoggerMock.debugCalled) - expectation.fulfill() - } - ) - .store(in: &cancellables) - - wait(for: [expectation], timeout: 0.1) - } -} diff --git a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/PresentPaywallOperatorTests.swift b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/PresentPaywallOperatorTests.swift index 0aec273a9..8a78b6b5e 100644 --- a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/PresentPaywallOperatorTests.swift +++ b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/PresentPaywallOperatorTests.swift @@ -28,7 +28,7 @@ final class PresentPaywallOperatorTests: XCTestCase { } } .store(in: &cancellables) - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let messageHandler = PaywallMessageHandler( sessionEventsManager: dependencyContainer.sessionEventsManager, @@ -106,7 +106,7 @@ final class PresentPaywallOperatorTests: XCTestCase { } .store(in: &cancellables) - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let messageHandler = PaywallMessageHandler( sessionEventsManager: dependencyContainer.sessionEventsManager, diff --git a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/StorePresentationObjectsOperatorTests.swift b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/StorePresentationObjectsOperatorTests.swift index 6956a643e..b58146a3f 100644 --- a/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/StorePresentationObjectsOperatorTests.swift +++ b/Tests/SuperwallKitTests/Paywall Presentation/Internal Presentation/Operators/StorePresentationObjectsOperatorTests.swift @@ -14,7 +14,7 @@ final class StorePresentationObjectsOperatorTests: XCTestCase { @MainActor func test_storePresentationObjects() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let request = PresentationRequest.stub() let input = PresentablePipelineOutput( diff --git a/Tests/SuperwallKitTests/Paywall View Controller/Web View/Message Handling/PaywallMessageHandlerTests.swift b/Tests/SuperwallKitTests/Paywall View Controller/Web View/Message Handling/PaywallMessageHandlerTests.swift index 0ed7ea6f8..6c7f3e74d 100644 --- a/Tests/SuperwallKitTests/Paywall View Controller/Web View/Message Handling/PaywallMessageHandlerTests.swift +++ b/Tests/SuperwallKitTests/Paywall View Controller/Web View/Message Handling/PaywallMessageHandlerTests.swift @@ -11,7 +11,7 @@ import XCTest final class PaywallMessageHandlerTests: XCTestCase { @MainActor func test_handleTemplateParams() async { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let messageHandler = PaywallMessageHandler( sessionEventsManager: dependencyContainer.sessionEventsManager, factory: dependencyContainer @@ -35,7 +35,7 @@ final class PaywallMessageHandlerTests: XCTestCase { @MainActor func test_onReady() async { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let messageHandler = PaywallMessageHandler( sessionEventsManager: dependencyContainer.sessionEventsManager, factory: dependencyContainer @@ -60,7 +60,7 @@ final class PaywallMessageHandlerTests: XCTestCase { @MainActor func test_close() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let messageHandler = PaywallMessageHandler( sessionEventsManager: dependencyContainer.sessionEventsManager, factory: dependencyContainer @@ -82,7 +82,7 @@ final class PaywallMessageHandlerTests: XCTestCase { @MainActor func test_openUrl() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let messageHandler = PaywallMessageHandler( sessionEventsManager: dependencyContainer.sessionEventsManager, factory: dependencyContainer @@ -106,7 +106,7 @@ final class PaywallMessageHandlerTests: XCTestCase { @MainActor func test_openUrlInSafari() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let messageHandler = PaywallMessageHandler( sessionEventsManager: dependencyContainer.sessionEventsManager, factory: dependencyContainer @@ -130,7 +130,7 @@ final class PaywallMessageHandlerTests: XCTestCase { @MainActor func test_openDeepLink() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let messageHandler = PaywallMessageHandler( sessionEventsManager: dependencyContainer.sessionEventsManager, factory: dependencyContainer @@ -154,7 +154,7 @@ final class PaywallMessageHandlerTests: XCTestCase { @MainActor func test_restore() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let messageHandler = PaywallMessageHandler( sessionEventsManager: dependencyContainer.sessionEventsManager, factory: dependencyContainer @@ -176,7 +176,7 @@ final class PaywallMessageHandlerTests: XCTestCase { @MainActor func test_purchaseProduct() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let messageHandler = PaywallMessageHandler( sessionEventsManager: dependencyContainer.sessionEventsManager, factory: dependencyContainer @@ -199,7 +199,7 @@ final class PaywallMessageHandlerTests: XCTestCase { @MainActor func test_custom() { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let messageHandler = PaywallMessageHandler( sessionEventsManager: dependencyContainer.sessionEventsManager, factory: dependencyContainer diff --git a/Tests/SuperwallKitTests/Paywall View Controller/Web View/Templating/TemplateLogicTests.swift b/Tests/SuperwallKitTests/Paywall View Controller/Web View/Templating/TemplateLogicTests.swift index 6714da6c3..011a9af19 100644 --- a/Tests/SuperwallKitTests/Paywall View Controller/Web View/Templating/TemplateLogicTests.swift +++ b/Tests/SuperwallKitTests/Paywall View Controller/Web View/Templating/TemplateLogicTests.swift @@ -65,12 +65,10 @@ final class TemplateLogicTests: XCTestCase { let json = try! JSON(data: encodedData) let jsonArray = json.array! - print(jsonArray) - // MARK: Then XCTAssertEqual(jsonArray[0]["event_name"], "products") XCTAssertEqual(jsonArray[0]["products"][0]["productId"].stringValue, products.first!.id) - XCTAssertEqual(jsonArray[0]["products"][0]["product"].stringValue, products.first!.type.rawValue) + XCTAssertEqual(jsonArray[0]["products"][0]["product"].stringValue, products.first!.type.description) XCTAssertEqual(jsonArray[0]["products"].count, 1) XCTAssertEqual(jsonArray[1]["event_name"], "template_variables") @@ -124,12 +122,10 @@ final class TemplateLogicTests: XCTestCase { let json = try! JSON(data: encodedData) let jsonArray = json.array! - print(jsonArray) - // MARK: Then XCTAssertEqual(jsonArray[0]["event_name"], "products") XCTAssertEqual(jsonArray[0]["products"][0]["productId"].stringValue, products.first!.id) - XCTAssertEqual(jsonArray[0]["products"][0]["product"].stringValue, products.first!.type.rawValue) + XCTAssertEqual(jsonArray[0]["products"][0]["product"].stringValue, products.first!.type.description) XCTAssertEqual(jsonArray[0]["products"].count, 1) XCTAssertEqual(jsonArray[1]["event_name"], "template_variables") @@ -191,16 +187,14 @@ final class TemplateLogicTests: XCTestCase { let json = try! JSON(data: encodedData) let jsonArray = json.array! - print(jsonArray) - // MARK: Then XCTAssertEqual(jsonArray[0]["event_name"], "products") XCTAssertEqual(jsonArray[0]["products"][0]["productId"].stringValue, products.first!.id) - XCTAssertEqual(jsonArray[0]["products"][0]["product"].stringValue, products.first!.type.rawValue) + XCTAssertEqual(jsonArray[0]["products"][0]["product"].stringValue, products.first!.type.description) XCTAssertEqual(jsonArray[0]["products"][1]["productId"].stringValue, products[1].id) - XCTAssertEqual(jsonArray[0]["products"][1]["product"].stringValue, products[1].type.rawValue) + XCTAssertEqual(jsonArray[0]["products"][1]["product"].stringValue, products[1].type.description) XCTAssertEqual(jsonArray[0]["products"][2]["productId"].stringValue, products[2].id) - XCTAssertEqual(jsonArray[0]["products"][2]["product"].stringValue, products[2].type.rawValue) + XCTAssertEqual(jsonArray[0]["products"][2]["product"].stringValue, products[2].type.description) XCTAssertEqual(jsonArray[0]["products"].count, 3) XCTAssertEqual(jsonArray[1]["event_name"], "template_variables") @@ -268,16 +262,14 @@ final class TemplateLogicTests: XCTestCase { let json = try! JSON(data: encodedData) let jsonArray = json.array! - print(jsonArray) - // MARK: Then XCTAssertEqual(jsonArray[0]["event_name"], "products") XCTAssertEqual(jsonArray[0]["products"][0]["productId"].stringValue, products.first!.id) - XCTAssertEqual(jsonArray[0]["products"][0]["product"].stringValue, products.first!.type.rawValue) + XCTAssertEqual(jsonArray[0]["products"][0]["product"].stringValue, products.first!.type.description) XCTAssertEqual(jsonArray[0]["products"][1]["productId"].stringValue, products[1].id) - XCTAssertEqual(jsonArray[0]["products"][1]["product"].stringValue, products[1].type.rawValue) + XCTAssertEqual(jsonArray[0]["products"][1]["product"].stringValue, products[1].type.description) XCTAssertEqual(jsonArray[0]["products"][2]["productId"].stringValue, products[2].id) - XCTAssertEqual(jsonArray[0]["products"][2]["product"].stringValue, products[2].type.rawValue) + XCTAssertEqual(jsonArray[0]["products"][2]["product"].stringValue, products[2].type.description) XCTAssertEqual(jsonArray[0]["products"].count, 3) XCTAssertEqual(jsonArray[1]["event_name"], "template_variables") diff --git a/Tests/SuperwallKitTests/Storage/Core Data/CoreDataStackMock.swift b/Tests/SuperwallKitTests/Storage/Core Data/CoreDataStackMock.swift index 0aa19063d..b0f037d6b 100644 --- a/Tests/SuperwallKitTests/Storage/Core Data/CoreDataStackMock.swift +++ b/Tests/SuperwallKitTests/Storage/Core Data/CoreDataStackMock.swift @@ -11,26 +11,6 @@ import CoreData @available(iOS 14.0, *) final class CoreDataStackMock: CoreDataStack { - override init() { - super.init() - let container = NSPersistentContainer( - name: CoreDataStack.modelName, - managedObjectModel: CoreDataStack.managedObject - ) - - let persistentStoreDescription = NSPersistentStoreDescription() - persistentStoreDescription.type = NSSQLiteStoreType - container.persistentStoreDescriptions = [persistentStoreDescription] - - container.loadPersistentStores { _, error in - if let error = error as NSError? { - fatalError("Unresolved error \(error), \(error.userInfo)") - } - } - - persistentContainer = container - } - func deleteAllEntities(named entityName: String) { let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: entityName) let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) diff --git a/Tests/SuperwallKitTests/Storage/StorageMock.swift b/Tests/SuperwallKitTests/Storage/StorageMock.swift index e50bf9752..bee8dd7e6 100644 --- a/Tests/SuperwallKitTests/Storage/StorageMock.swift +++ b/Tests/SuperwallKitTests/Storage/StorageMock.swift @@ -16,6 +16,12 @@ final class StorageMock: Storage { var internalConfirmedAssignments: [Experiment.ID: Experiment.Variant] var didClearCachedSessionEvents = false + class DeviceInfoFactoryMock: DeviceInfoFactory { + func makeDeviceInfo() -> DeviceInfo { + return DeviceInfo(appInstalledAtString: "a", locale: "b") + } + } + init( internalCachedTriggerSessions: [TriggerSession] = [], internalCachedTransactions: [StoreTransaction] = [], @@ -27,7 +33,7 @@ final class StorageMock: Storage { self.internalCachedTransactions = internalCachedTransactions self.internalConfirmedAssignments = confirmedAssignments - super.init(cache: cache, coreDataManager: coreDataManager) + super.init(factory: DeviceInfoFactoryMock(), cache: cache, coreDataManager: coreDataManager) } override func get(_ keyType: Key.Type) -> Key.Value? where Key : Storable { diff --git a/Tests/SuperwallKitTests/StoreKit/Products/Receipt Manager/ReceiptManagerTests.swift b/Tests/SuperwallKitTests/StoreKit/Products/Receipt Manager/ReceiptManagerTests.swift index 3933b0384..7b2499d9b 100644 --- a/Tests/SuperwallKitTests/StoreKit/Products/Receipt Manager/ReceiptManagerTests.swift +++ b/Tests/SuperwallKitTests/StoreKit/Products/Receipt Manager/ReceiptManagerTests.swift @@ -10,20 +10,21 @@ import XCTest @testable import SuperwallKit class ReceiptManagerTests: XCTestCase { + var storeKitCoordinatorFactoryMock: StoreKitCoordinatorFactoryMock! + // MARK: - loadPurchasedProducts private func makeStoreKitManager(with productsFetcher: ProductsFetcherSK1) -> StoreKitManager { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let coordinator = StoreKitCoordinator( delegateAdapter: dependencyContainer.delegateAdapter, storeKitManager: dependencyContainer.storeKitManager, factory: dependencyContainer, productsFetcher: productsFetcher ) - let storeKitCoordinatorFactoryMock = StoreKitCoordinatorFactoryMock( + storeKitCoordinatorFactoryMock = StoreKitCoordinatorFactoryMock( coordinator: coordinator ) let storeKitManager = StoreKitManager(factory: storeKitCoordinatorFactoryMock) - storeKitManager.postInit() return storeKitManager } diff --git a/Tests/SuperwallKitTests/StoreKit/StoreKitManagerTests.swift b/Tests/SuperwallKitTests/StoreKit/StoreKitManagerTests.swift index a94ef2b98..d74f9a6b4 100644 --- a/Tests/SuperwallKitTests/StoreKit/StoreKitManagerTests.swift +++ b/Tests/SuperwallKitTests/StoreKit/StoreKitManagerTests.swift @@ -11,26 +11,27 @@ import XCTest import StoreKit class StoreKitManagerTests: XCTestCase { + var storeKitCoordinatorFactoryMock: StoreKitCoordinatorFactoryMock! + private func makeStoreKitManager(with productsFetcher: ProductsFetcherSK1) -> StoreKitManager { - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() let coordinator = StoreKitCoordinator( delegateAdapter: dependencyContainer.delegateAdapter, storeKitManager: dependencyContainer.storeKitManager, factory: dependencyContainer, productsFetcher: productsFetcher ) - let storeKitCoordinatorFactoryMock = StoreKitCoordinatorFactoryMock( + storeKitCoordinatorFactoryMock = StoreKitCoordinatorFactoryMock( coordinator: coordinator ) let storeKitManager = StoreKitManager(factory: storeKitCoordinatorFactoryMock) - storeKitManager.postInit() return storeKitManager } func test_getProducts_primaryProduct() async { - let dependencyContainer = DependencyContainer(apiKey: "abc") - let manager = dependencyContainer.storeKitManager! + let dependencyContainer = DependencyContainer() + let manager = dependencyContainer.storeKitManager let primary = MockSkProduct(productIdentifier: "abc") let substituteProducts = PaywallProducts( @@ -48,8 +49,8 @@ class StoreKitManagerTests: XCTestCase { } func test_getProducts_primaryAndTertiaryProduct() async { - let dependencyContainer = DependencyContainer(apiKey: "abc") - let manager = dependencyContainer.storeKitManager! + let dependencyContainer = DependencyContainer() + let manager = dependencyContainer.storeKitManager let primary = MockSkProduct(productIdentifier: "abc") let tertiary = MockSkProduct(productIdentifier: "def") @@ -73,8 +74,8 @@ class StoreKitManagerTests: XCTestCase { } func test_getProducts_primarySecondaryTertiaryProduct() async { - let dependencyContainer = DependencyContainer(apiKey: "abc") - let manager = dependencyContainer.storeKitManager! + let dependencyContainer = DependencyContainer() + let manager = dependencyContainer.storeKitManager let primary = MockSkProduct(productIdentifier: "abc") let secondary = MockSkProduct(productIdentifier: "def") diff --git a/Tests/SuperwallKitTests/StoreKit/Transactions/Purchasing/ProductPurchaserSK1Tests.swift b/Tests/SuperwallKitTests/StoreKit/Transactions/Purchasing/ProductPurchaserSK1Tests.swift index a7228d009..d800cf6b5 100644 --- a/Tests/SuperwallKitTests/StoreKit/Transactions/Purchasing/ProductPurchaserSK1Tests.swift +++ b/Tests/SuperwallKitTests/StoreKit/Transactions/Purchasing/ProductPurchaserSK1Tests.swift @@ -15,7 +15,7 @@ final class ProductPurchaserSK1Tests: XCTestCase { // TODO: Can't use this because the recording is done on a background priority thread. Github computer keeps failing this, despite it actually passing on the computer. /*func test_recordTransaction() async { // MARK: Given - let dependencyContainer = DependencyContainer(apiKey: "") + let dependencyContainer = DependencyContainer() // Set up App Session let appSessionId = "123"