diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c13e9450..3268ad01d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,23 @@ The changelog for `SuperwallKit`. Also see the [releases](https://github.com/superwall-me/Superwall-iOS/releases) on GitHub. +## 3.4.7 + +### Enhancements + +- SW-2667: Adds `preferredLanguageCode` and `preferredLocale` to device attributes. If your app isn't already localized for a language you're trying to target, the `deviceLanguageCode` and `deviceLocale` may not be what you're expecting. Use these device attributes instead to access the first preferred locale the user has in their device settings. + +### Fixes + +- Fixes bug where a `transaction_abandon` or `transaction_fail` event would prevent the presented paywall from dismissing if `paywall_decline` was a trigger. +- SW-2678: Fixes issue where the `subscription_start` event was being fired even if a non-recurring product was purchased. +- SW-2659: Fixes issue on macOS where the window behind a paywall wasn't being removed when a paywall was dismissed, leading to the app appearing to be in a frozen state. + ## 3.4.6 ### Enhancements -- Adds internal code for SDK wrappers like flutter. +- Adds internal code for SDK wrappers like Flutter. ## 3.4.5 diff --git a/Sources/SuperwallKit/Analytics/Internal Tracking/Tracking.swift b/Sources/SuperwallKit/Analytics/Internal Tracking/Tracking.swift index 9721a9891..fae718355 100644 --- a/Sources/SuperwallKit/Analytics/Internal Tracking/Tracking.swift +++ b/Sources/SuperwallKit/Analytics/Internal Tracking/Tracking.swift @@ -58,7 +58,7 @@ extension Superwall { disableVerboseEvents: verboseEvents, isSandbox: dependencyContainer.makeIsSandbox() ) { - await dependencyContainer.queue.enqueue(event: eventData.jsonData) + await dependencyContainer.eventsQueue.enqueue(event: eventData.jsonData) } dependencyContainer.storage.coreDataManager.saveEventData(eventData) diff --git a/Sources/SuperwallKit/Dependencies/DependencyContainer.swift b/Sources/SuperwallKit/Dependencies/DependencyContainer.swift index 8bc879725..7eb332ae5 100644 --- a/Sources/SuperwallKit/Dependencies/DependencyContainer.swift +++ b/Sources/SuperwallKit/Dependencies/DependencyContainer.swift @@ -30,7 +30,7 @@ final class DependencyContainer { var paywallManager: PaywallManager! var paywallRequestManager: PaywallRequestManager! var deviceHelper: DeviceHelper! - var queue: EventsQueue! + var eventsQueue: EventsQueue! var debugManager: DebugManager! var api: Api! var transactionManager: TransactionManager! @@ -83,7 +83,7 @@ final class DependencyContainer { factory: self ) - queue = EventsQueue( + eventsQueue = EventsQueue( network: network, configManager: configManager ) @@ -124,6 +124,7 @@ final class DependencyContainer { receiptManager: receiptManager, purchaseController: purchaseController, sessionEventsManager: sessionEventsManager, + eventsQueue: eventsQueue, factory: self ) diff --git a/Sources/SuperwallKit/Misc/Constants.swift b/Sources/SuperwallKit/Misc/Constants.swift index 4f3a2dce8..c065c73bd 100644 --- a/Sources/SuperwallKit/Misc/Constants.swift +++ b/Sources/SuperwallKit/Misc/Constants.swift @@ -18,5 +18,5 @@ let sdkVersion = """ */ let sdkVersion = """ -3.4.6 +3.4.7 """ diff --git a/Sources/SuperwallKit/Models/Triggers/Experiment.swift b/Sources/SuperwallKit/Models/Triggers/Experiment.swift index 35708a2c6..8f92b379e 100644 --- a/Sources/SuperwallKit/Models/Triggers/Experiment.swift +++ b/Sources/SuperwallKit/Models/Triggers/Experiment.swift @@ -68,7 +68,7 @@ public final class Experiment: NSObject, Codable, Sendable { /// The identifier of the paywall variant. Only valid when the variant `type` is `treatment`. public let paywallId: String? - public init(id: String, type: VariantType, paywallId: String?) { + init(id: String, type: VariantType, paywallId: String?) { self.id = id self.type = type self.paywallId = paywallId @@ -100,7 +100,7 @@ public final class Experiment: NSObject, Codable, Sendable { case paywallId = "paywall_identifier" } - public init( + init( id: String, groupId: String, variant: Variant diff --git a/Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift b/Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift index a822457de..2a2272434 100644 --- a/Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift +++ b/Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift @@ -15,6 +15,14 @@ class DeviceHelper { let localeIdentifier = factory.makeLocaleIdentifier() return localeIdentifier ?? Locale.autoupdatingCurrent.identifier } + + var preferredLocale: String { + guard let preferredIdentifier = Locale.preferredLanguages.first else { + return locale + } + return Locale(identifier: preferredIdentifier).identifier + } + let appInstalledAtString: String private let reachability: SCNetworkReachability? @@ -57,7 +65,22 @@ class DeviceHelper { }() var languageCode: String { - Locale.autoupdatingCurrent.languageCode ?? "" + if #available(iOS 16, *) { + return Locale.autoupdatingCurrent.language.languageCode?.identifier ?? "" + } else { + return Locale.autoupdatingCurrent.languageCode ?? "" + } + } + + var preferredLanguageCode: String { + guard let preferredIdentifier = Locale.preferredLanguages.first else { + return languageCode + } + if #available(iOS 16, *) { + return Locale(identifier: preferredIdentifier).language.languageCode?.identifier ?? "" + } else { + return Locale(identifier: preferredIdentifier).languageCode ?? "" + } } var currencyCode: String { @@ -400,7 +423,9 @@ class DeviceHelper { osVersion: osVersion, deviceModel: model, deviceLocale: locale, + preferredLocale: preferredLocale, deviceLanguageCode: languageCode, + preferredLanguageCode: preferredLanguageCode, deviceCurrencyCode: currencyCode, deviceCurrencySymbol: currencySymbol, interfaceType: interfaceType, diff --git a/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/GetPresenter.swift b/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/GetPresenter.swift index f827763b7..c79227895 100644 --- a/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/GetPresenter.swift +++ b/Sources/SuperwallKit/Paywall/Presentation/Internal/Operators/GetPresenter.swift @@ -82,8 +82,7 @@ extension Superwall { logLevel: .error, scope: .paywallPresentation, message: "No Presenter To Present Paywall", - info: debugInfo, - error: nil + info: debugInfo ) let error = InternalPresentationLogic.presentationError( @@ -143,6 +142,7 @@ extension Superwall { @MainActor func destroyPresentingWindow() { + presentationItems.window?.windowScene = nil presentationItems.window = nil } } diff --git a/Sources/SuperwallKit/Paywall/View Controller/PaywallViewController.swift b/Sources/SuperwallKit/Paywall/View Controller/PaywallViewController.swift index d8f0b4198..226aba21e 100644 --- a/Sources/SuperwallKit/Paywall/View Controller/PaywallViewController.swift +++ b/Sources/SuperwallKit/Paywall/View Controller/PaywallViewController.swift @@ -787,14 +787,18 @@ extension PaywallViewController { ) let paywallPresenterEvent = info.presentedByEventWithName let presentedByPaywallDecline = paywallPresenterEvent == SuperwallEventObjc.paywallDecline.description + let presentedByTransactionAbandon = paywallPresenterEvent == SuperwallEventObjc.transactionAbandon.description + let presentedByTransactionFail = paywallPresenterEvent == SuperwallEventObjc.transactionFail.description await Superwall.shared.track(trackedEvent) if case .paywall = presentationResult, - !presentedByPaywallDecline { + !presentedByPaywallDecline, + !presentedByTransactionAbandon, + !presentedByTransactionFail { // If a paywall_decline trigger is active and the current paywall wasn't presented - // by paywall_decline, it lands here so as not to dismiss the paywall. - // track() will do that before presenting the next paywall. + // by paywall_decline, transaction_abandon, or transaction_fail, it lands here so + // as not to dismiss the paywall. track() will do that before presenting the next paywall. return } } 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 index a4005d649..26d7de898 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 @@ -119,8 +119,7 @@ final class PaywallMessageHandler: WebEventDelegate { logLevel: .debug, scope: .paywallViewController, message: "Posting Message", - info: ["message": messageScript], - error: nil + info: ["message": messageScript] ) delegate?.webView.evaluateJavaScript(messageScript) { _, error in 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 index 5af149b3f..e3ac7a515 100644 --- a/Sources/SuperwallKit/Paywall/View Controller/Web View/Templating/Models/DeviceTemplate.swift +++ b/Sources/SuperwallKit/Paywall/View Controller/Web View/Templating/Models/DeviceTemplate.swift @@ -17,7 +17,9 @@ struct DeviceTemplate: Codable { var osVersion: String var deviceModel: String var deviceLocale: String + var preferredLocale: String var deviceLanguageCode: String + var preferredLanguageCode: String var deviceCurrencyCode: String var deviceCurrencySymbol: String var interfaceType: String diff --git a/Sources/SuperwallKit/Storage/EventsQueue.swift b/Sources/SuperwallKit/Storage/EventsQueue.swift index 275e18860..9d3268993 100644 --- a/Sources/SuperwallKit/Storage/EventsQueue.swift +++ b/Sources/SuperwallKit/Storage/EventsQueue.swift @@ -82,7 +82,7 @@ actor EventsQueue { return true } - private func flushInternal(depth: Int = 10) { + func flushInternal(depth: Int = 10) { var eventsToSend: [JSON] = [] var i = 0 diff --git a/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift b/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift index 3133e09b7..88fecdc04 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/TransactionManager.swift @@ -15,6 +15,7 @@ final class TransactionManager { private let receiptManager: ReceiptManager private let purchaseController: PurchaseController private let sessionEventsManager: SessionEventsManager + private let eventsQueue: EventsQueue private let factory: Factory typealias Factory = OptionsFactory & TriggerFactory @@ -27,12 +28,14 @@ final class TransactionManager { receiptManager: ReceiptManager, purchaseController: PurchaseController, sessionEventsManager: SessionEventsManager, + eventsQueue: EventsQueue, factory: Factory ) { self.storeKitManager = storeKitManager self.receiptManager = receiptManager self.purchaseController = purchaseController self.sessionEventsManager = sessionEventsManager + self.eventsQueue = eventsQueue self.factory = factory } @@ -387,32 +390,35 @@ final class TransactionManager { ) await Superwall.shared.track(trackedEvent) + // Immediately flush the events queue on transaction complete. + await eventsQueue.flushInternal() + if product.subscriptionPeriod == nil { let trackedEvent = InternalSuperwallEvent.NonRecurringProductPurchase( paywallInfo: paywallInfo, product: product ) await Superwall.shared.track(trackedEvent) - } + } else { + if didStartFreeTrial { + let trackedEvent = InternalSuperwallEvent.FreeTrialStart( + paywallInfo: paywallInfo, + product: product + ) + await Superwall.shared.track(trackedEvent) - if didStartFreeTrial { - let trackedEvent = InternalSuperwallEvent.FreeTrialStart( - paywallInfo: paywallInfo, - product: product - ) - await Superwall.shared.track(trackedEvent) + let notifications = paywallInfo.localNotifications.filter { + $0.type == .trialStarted + } - let notifications = paywallInfo.localNotifications.filter { - $0.type == .trialStarted + await NotificationScheduler.scheduleNotifications(notifications, factory: factory) + } else { + let trackedEvent = InternalSuperwallEvent.SubscriptionStart( + paywallInfo: paywallInfo, + product: product + ) + await Superwall.shared.track(trackedEvent) } - - await NotificationScheduler.scheduleNotifications(notifications, factory: factory) - } else { - let trackedEvent = InternalSuperwallEvent.SubscriptionStart( - paywallInfo: paywallInfo, - product: product - ) - await Superwall.shared.track(trackedEvent) } } }