diff --git a/RevenueCatUI/PaywallView.swift b/RevenueCatUI/PaywallView.swift index a5967b1d96..296d779f5f 100644 --- a/RevenueCatUI/PaywallView.swift +++ b/RevenueCatUI/PaywallView.swift @@ -301,8 +301,8 @@ struct LoadedOfferingPaywallView: View { var body: some View { // Note: preferences need to be applied after `.toolbar` call self.content - .preference(key: PurchasedInProgressPreferenceKey.self, - value: self.purchaseHandler.purchaseInProgress) + .preference(key: PurchaseInProgressPreferenceKey.self, + value: self.purchaseHandler.packageBeingPurchased) .preference(key: PurchasedResultPreferenceKey.self, value: .init(data: self.purchaseHandler.purchaseResult)) .preference(key: RestoredCustomerInfoPreferenceKey.self, diff --git a/RevenueCatUI/Purchasing/PurchaseHandler.swift b/RevenueCatUI/Purchasing/PurchaseHandler.swift index 66a167f075..688addac1d 100644 --- a/RevenueCatUI/Purchasing/PurchaseHandler.swift +++ b/RevenueCatUI/Purchasing/PurchaseHandler.swift @@ -25,7 +25,7 @@ final class PurchaseHandler: ObservableObject { /// Whether a purchase is currently in progress @Published - fileprivate(set) var purchaseInProgress: Bool = false + fileprivate(set) var packageBeingPurchased: Package? /// Whether a purchase or restore is currently in progress @Published @@ -84,13 +84,13 @@ extension PurchaseHandler { @MainActor func purchase(package: Package) async throws -> PurchaseResultData { - self.purchaseInProgress = true + self.packageBeingPurchased = package self.purchaseResult = nil self.purchaseError = nil self.startAction() defer { - self.purchaseInProgress = false + self.packageBeingPurchased = nil self.actionInProgress = false } @@ -244,11 +244,11 @@ private final class NotConfiguredPurchases: PaywallPurchasesType { // MARK: - Preference Keys @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -struct PurchasedInProgressPreferenceKey: PreferenceKey { +struct PurchaseInProgressPreferenceKey: PreferenceKey { - static var defaultValue: Bool = false + static var defaultValue: Package? - static func reduce(value: inout Bool, nextValue: () -> Bool) { + static func reduce(value: inout Package?, nextValue: () -> Package?) { value = nextValue() } diff --git a/RevenueCatUI/UIKit/PaywallViewController.swift b/RevenueCatUI/UIKit/PaywallViewController.swift index 1305bcabc8..ce1b62e168 100644 --- a/RevenueCatUI/UIKit/PaywallViewController.swift +++ b/RevenueCatUI/UIKit/PaywallViewController.swift @@ -181,6 +181,11 @@ public protocol PaywallViewControllerDelegate: AnyObject { @objc(paywallViewControllerDidStartPurchase:) optional func paywallViewControllerDidStartPurchase(_ controller: PaywallViewController) + /// Notifies that a purchase has started in a ``PaywallViewController``. + @objc(paywallViewController:didStartPurchaseWithPackage:) + optional func paywallViewController(_ controller: PaywallViewController, + didStartPurchaseWith package: Package) + /// Notifies that a purchase has completed in a ``PaywallViewController``. @objc(paywallViewController:didFinishPurchasingWithCustomerInfo:) optional func paywallViewController(_ controller: PaywallViewController, @@ -238,9 +243,10 @@ private extension PaywallViewController { func createHostingController() -> UIHostingController { let container = PaywallContainerView( configuration: self.configuration, - purchaseStarted: { [weak self] in + purchaseStarted: { [weak self] package in guard let self else { return } self.delegate?.paywallViewControllerDidStartPurchase?(self) + self.delegate?.paywallViewController?(self, didStartPurchaseWith: package) }, purchaseCompleted: { [weak self] transaction, customerInfo in guard let self else { return } @@ -292,7 +298,7 @@ private struct PaywallContainerView: View { var configuration: PaywallViewConfiguration - let purchaseStarted: PurchaseStartedHandler + let purchaseStarted: PurchaseOfPackageStartedHandler let purchaseCompleted: PurchaseCompletedHandler let purchaseCancelled: PurchaseCancelledHandler let restoreCompleted: PurchaseOrRestoreCompletedHandler diff --git a/RevenueCatUI/View+PresentPaywall.swift b/RevenueCatUI/View+PresentPaywall.swift index 448d0686c0..3e269d849c 100644 --- a/RevenueCatUI/View+PresentPaywall.swift +++ b/RevenueCatUI/View+PresentPaywall.swift @@ -37,6 +37,7 @@ extension PaywallPresentationMode { } +// swiftlint:disable file_length @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) @available(macOS, unavailable, message: "RevenueCatUI does not support macOS yet") @available(tvOS, unavailable, message: "RevenueCatUI does not support tvOS yet") @@ -44,6 +45,7 @@ extension View { typealias CustomerInfoFetcher = @Sendable () async throws -> CustomerInfo + // swiftlint:disable line_length /// Presents a ``PaywallView`` if the given entitlement identifier is not active /// in the current environment for the current `CustomerInfo`. /// ```swift @@ -65,18 +67,77 @@ extension View { /// [Documentation](https://rev.cat/paywalls) /// /// - Tag: presentPaywallIfNeeded + @available(iOS, deprecated: 1, renamed: "presentPaywallIfNeeded(requiredEntitlementIdentifier:offering:fonts:presentationMode:purchaseStarted:purchaseCompleted:purchaseCancelled:restoreStarted:restoreCompleted:purchaseFailure:restoreFailure:onDismiss:)") + @available(tvOS, deprecated: 1, renamed: "presentPaywallIfNeeded(requiredEntitlementIdentifier:offering:fonts:presentationMode:purchaseStarted:purchaseCompleted:purchaseCancelled:restoreStarted:restoreCompleted:purchaseFailure:restoreFailure:onDismiss:)") + @available(watchOS, deprecated: 1, renamed: "presentPaywallIfNeeded(requiredEntitlementIdentifier:offering:fonts:presentationMode:purchaseStarted:purchaseCompleted:purchaseCancelled:restoreStarted:restoreCompleted:purchaseFailure:restoreFailure:onDismiss:)") + @available(macOS, deprecated: 1, renamed: "presentPaywallIfNeeded(requiredEntitlementIdentifier:offering:fonts:presentationMode:purchaseStarted:purchaseCompleted:purchaseCancelled:restoreStarted:restoreCompleted:purchaseFailure:restoreFailure:onDismiss:)") + @available(macCatalyst, deprecated: 1, renamed: "presentPaywallIfNeeded(requiredEntitlementIdentifier:offering:fonts:presentationMode:purchaseStarted:purchaseCompleted:purchaseCancelled:restoreStarted:restoreCompleted:purchaseFailure:restoreFailure:onDismiss:)") + // swiftlint:enable line_length public func presentPaywallIfNeeded( requiredEntitlementIdentifier: String, offering: Offering? = nil, fonts: PaywallFontProvider = DefaultPaywallFontProvider(), presentationMode: PaywallPresentationMode = .default, - purchaseStarted: PurchaseStartedHandler? = nil, + purchaseStarted: @escaping PurchaseStartedHandler, purchaseCompleted: PurchaseOrRestoreCompletedHandler? = nil, purchaseCancelled: PurchaseCancelledHandler? = nil, restoreCompleted: PurchaseOrRestoreCompletedHandler? = nil, purchaseFailure: PurchaseFailureHandler? = nil, restoreFailure: PurchaseFailureHandler? = nil, onDismiss: (() -> Void)? = nil + ) -> some View { + return self.presentPaywallIfNeeded( + requiredEntitlementIdentifier: requiredEntitlementIdentifier, + offering: offering, + fonts: fonts, + presentationMode: presentationMode, + purchaseStarted: { _ in + purchaseStarted() + }, + purchaseCompleted: purchaseCompleted, + purchaseCancelled: purchaseCancelled, + restoreStarted: nil, + restoreCompleted: restoreCompleted, + purchaseFailure: purchaseFailure, + restoreFailure: restoreFailure, + onDismiss: onDismiss + ) + } + + /// Presents a ``PaywallView`` if the given entitlement identifier is not active + /// in the current environment for the current `CustomerInfo`. + /// ```swift + /// var body: some View { + /// YourApp() + /// .presentPaywallIfNeeded(requiredEntitlementIdentifier: "pro") + /// } + /// ``` + /// - Note: If loading the `CustomerInfo` fails (for example, if Internet is offline), + /// the paywall won't be displayed. + /// + /// - Parameter offering: The `Offering` containing the desired `PaywallData` to display. + /// If `nil` (the default), `Offerings.current` will be used. Note that specifying this parameter means + /// that it will ignore the offering configured in an active experiment. + /// - Parameter fonts: An optional ``PaywallFontProvider``. + /// - Parameter presentationMode: The desired presentation mode of the paywall. Defaults to `.sheet`. + /// + /// ### Related Articles + /// [Documentation](https://rev.cat/paywalls) + /// + /// - Tag: presentPaywallIfNeeded + public func presentPaywallIfNeeded( + requiredEntitlementIdentifier: String, + offering: Offering? = nil, + fonts: PaywallFontProvider = DefaultPaywallFontProvider(), + presentationMode: PaywallPresentationMode = .default, + purchaseStarted: PurchaseOfPackageStartedHandler? = nil, + purchaseCompleted: PurchaseOrRestoreCompletedHandler? = nil, + purchaseCancelled: PurchaseCancelledHandler? = nil, + restoreStarted: RestoreStartedHandler? = nil, + restoreCompleted: PurchaseOrRestoreCompletedHandler? = nil, + purchaseFailure: PurchaseFailureHandler? = nil, + restoreFailure: PurchaseFailureHandler? = nil, + onDismiss: (() -> Void)? = nil ) -> some View { return self.presentPaywallIfNeeded( offering: offering, @@ -98,6 +159,7 @@ extension View { ) } + // swiftlint:disable line_length /// Presents a ``PaywallView`` based a given condition. /// Example: /// ```swift @@ -134,12 +196,18 @@ extension View { /// /// ### Related Articles /// [Documentation](https://rev.cat/paywalls) + @available(iOS, deprecated: 1, renamed: "presentPaywallIfNeeded(offering:fonts:presentationMode:shouldDisplay:purchaseStarted:purchaseCompleted:purchaseCancelled:restoreStarted:restoreCompleted:purchaseFailure:restoreFailure:onDismiss:)") + @available(tvOS, deprecated: 1, renamed: "presentPaywallIfNeeded(offering:fonts:presentationMode:shouldDisplay:purchaseStarted:purchaseCompleted:purchaseCancelled:restoreStarted:restoreCompleted:purchaseFailure:restoreFailure:onDismiss:)") + @available(watchOS, deprecated: 1, renamed: "presentPaywallIfNeeded(offering:fonts:presentationMode:shouldDisplay:purchaseStarted:purchaseCompleted:purchaseCancelled:restoreStarted:restoreCompleted:purchaseFailure:restoreFailure:onDismiss:)") + @available(macOS, deprecated: 1, renamed: "presentPaywallIfNeeded(offering:fonts:presentationMode:shouldDisplay:purchaseStarted:purchaseCompleted:purchaseCancelled:restoreStarted:restoreCompleted:purchaseFailure:restoreFailure:onDismiss:)") + @available(macCatalyst, deprecated: 1, renamed: "presentPaywallIfNeeded(offering:fonts:presentationMode:shouldDisplay:purchaseStarted:purchaseCompleted:purchaseCancelled:restoreStarted:restoreCompleted:purchaseFailure:restoreFailure:onDismiss:)") + // swiftlint:enable line_length public func presentPaywallIfNeeded( offering: Offering? = nil, fonts: PaywallFontProvider = DefaultPaywallFontProvider(), presentationMode: PaywallPresentationMode = .default, shouldDisplay: @escaping @Sendable (CustomerInfo) -> Bool, - purchaseStarted: PurchaseStartedHandler? = nil, + purchaseStarted: @escaping PurchaseStartedHandler, purchaseCompleted: PurchaseOrRestoreCompletedHandler? = nil, purchaseCancelled: PurchaseCancelledHandler? = nil, restoreCompleted: PurchaseOrRestoreCompletedHandler? = nil, @@ -152,7 +220,9 @@ extension View { fonts: fonts, presentationMode: presentationMode, shouldDisplay: shouldDisplay, - purchaseStarted: purchaseStarted, + purchaseStarted: { _ in + purchaseStarted() + }, purchaseCompleted: purchaseCompleted, purchaseCancelled: purchaseCancelled, restoreStarted: nil, @@ -170,6 +240,81 @@ extension View { ) } + /// Presents a ``PaywallView`` based a given condition. + /// Example: + /// ```swift + /// var body: some View { + /// YourApp() + /// .presentPaywallIfNeeded { + /// !$0.entitlements.active.keys.contains("entitlement_identifier") + /// } purchaseStarted: { package in + /// print("Purchase started \(package)") + /// } purchaseCompleted: { customerInfo in + /// print("Customer info unlocked entitlement: \(customerInfo.entitlements)") + /// } purchaseCancelled: { + /// print("Purchase was cancelled") + /// } restoreStarted: { + /// print("Restore started") + /// } restoreCompleted: { customerInfo in + /// // If `entitlement_identifier` is active, paywall will dismiss automatically. + /// print("Purchases restored") + /// } purchaseFailure: { error in + /// print("Error purchasing: \(error)") + /// } restoreFailure: { error in + /// print("Error restoring purchases: \(error)") + /// } onDismiss: { + /// print("Paywall was dismissed either manually or automatically after a purchase.") + /// } + /// } + /// ``` + /// - Note: If loading the `CustomerInfo` fails (for example, if Internet is offline), + /// the paywall won't be displayed. + /// + /// - Parameter offering: The `Offering` containing the desired `PaywallData` to display. + /// If `nil` (the default), `Offerings.current` will be used. Note that specifying this parameter means + /// that it will ignore the offering configured in an active experiment. + /// - Parameter fonts: An optional ``PaywallFontProvider``. + /// - Parameter presentationMode: The desired presentation mode of the paywall. Defaults to `.sheet`. + /// + /// ### Related Articles + /// [Documentation](https://rev.cat/paywalls) + public func presentPaywallIfNeeded( + offering: Offering? = nil, + fonts: PaywallFontProvider = DefaultPaywallFontProvider(), + presentationMode: PaywallPresentationMode = .default, + shouldDisplay: @escaping @Sendable (CustomerInfo) -> Bool, + purchaseStarted: PurchaseOfPackageStartedHandler? = nil, + purchaseCompleted: PurchaseOrRestoreCompletedHandler? = nil, + purchaseCancelled: PurchaseCancelledHandler? = nil, + restoreStarted: RestoreStartedHandler? = nil, + restoreCompleted: PurchaseOrRestoreCompletedHandler? = nil, + purchaseFailure: PurchaseFailureHandler? = nil, + restoreFailure: PurchaseFailureHandler? = nil, + onDismiss: (() -> Void)? = nil + ) -> some View { + return self.presentPaywallIfNeeded( + offering: offering, + fonts: fonts, + presentationMode: presentationMode, + shouldDisplay: shouldDisplay, + purchaseStarted: purchaseStarted, + purchaseCompleted: purchaseCompleted, + purchaseCancelled: purchaseCancelled, + restoreStarted: restoreStarted, + restoreCompleted: restoreCompleted, + purchaseFailure: purchaseFailure, + restoreFailure: restoreFailure, + onDismiss: onDismiss, + customerInfoFetcher: { + guard Purchases.isConfigured else { + throw PaywallError.purchasesNotConfigured + } + + return try await Purchases.shared.customerInfo() + } + ) + } + // Visible overload for tests func presentPaywallIfNeeded( offering: Offering? = nil, @@ -178,7 +323,7 @@ extension View { purchaseHandler: PurchaseHandler? = nil, presentationMode: PaywallPresentationMode = .default, shouldDisplay: @escaping @Sendable (CustomerInfo) -> Bool, - purchaseStarted: PurchaseStartedHandler? = nil, + purchaseStarted: PurchaseOfPackageStartedHandler? = nil, purchaseCompleted: PurchaseOrRestoreCompletedHandler? = nil, purchaseCancelled: PurchaseCancelledHandler? = nil, restoreStarted: RestoreStartedHandler? = nil, @@ -222,7 +367,7 @@ private struct PresentingPaywallModifier: ViewModifier { var shouldDisplay: @Sendable (CustomerInfo) -> Bool var presentationMode: PaywallPresentationMode - var purchaseStarted: PurchaseStartedHandler? + var purchaseStarted: PurchaseOfPackageStartedHandler? var purchaseCompleted: PurchaseOrRestoreCompletedHandler? var purchaseCancelled: PurchaseCancelledHandler? var restoreCompleted: PurchaseOrRestoreCompletedHandler? @@ -240,7 +385,7 @@ private struct PresentingPaywallModifier: ViewModifier { init( shouldDisplay: @escaping @Sendable (CustomerInfo) -> Bool, presentationMode: PaywallPresentationMode, - purchaseStarted: PurchaseStartedHandler?, + purchaseStarted: PurchaseOfPackageStartedHandler?, purchaseCompleted: PurchaseOrRestoreCompletedHandler?, purchaseCancelled: PurchaseCancelledHandler?, restoreCompleted: PurchaseOrRestoreCompletedHandler?, @@ -319,7 +464,7 @@ private struct PresentingPaywallModifier: ViewModifier { ) ) .onPurchaseStarted { - self.purchaseStarted?() + self.purchaseStarted?($0) } .onPurchaseCompleted { self.purchaseCompleted?($0) diff --git a/RevenueCatUI/View+PresentPaywallFooter.swift b/RevenueCatUI/View+PresentPaywallFooter.swift index c4723eba96..ae5243be3d 100644 --- a/RevenueCatUI/View+PresentPaywallFooter.swift +++ b/RevenueCatUI/View+PresentPaywallFooter.swift @@ -19,6 +19,7 @@ import SwiftUI @available(iOS 15.0, macOS 12.0, tvOS 15.0, *) extension View { + // swiftlint:disable line_length /// Presents a ``PaywallFooterView`` at the bottom of a view that loads the `Offerings.current`. /// ```swift /// var body: some View { @@ -29,15 +30,57 @@ extension View { /// /// ### Related Articles /// [Documentation](https://rev.cat/paywalls) + @available(iOS, deprecated: 1, renamed: "paywallFooter(condensed:fonts:purchaseStarted:purchaseCompleted:purchaseCancelled:restoreStarted:restoreCompleted:purchaseFailure:restoreFailure:)") + @available(tvOS, deprecated: 1, renamed: "paywallFooter(condensed:fonts:purchaseStarted:purchaseCompleted:purchaseCancelled:restoreStarted:restoreCompleted:purchaseFailure:restoreFailure:)") + @available(watchOS, deprecated: 1, renamed: "paywallFooter(condensed:fonts:purchaseStarted:purchaseCompleted:purchaseCancelled:restoreStarted:restoreCompleted:purchaseFailure:restoreFailure:)") + @available(macOS, deprecated: 1, renamed: "paywallFooter(condensed:fonts:purchaseStarted:purchaseCompleted:purchaseCancelled:restoreStarted:restoreCompleted:purchaseFailure:restoreFailure:)") + @available(macCatalyst, deprecated: 1, renamed: "paywallFooter(condensed:fonts:purchaseStarted:purchaseCompleted:purchaseCancelled:restoreStarted:restoreCompleted:purchaseFailure:restoreFailure:)") + // swiftlint:enable line_length public func paywallFooter( condensed: Bool = false, fonts: PaywallFontProvider = DefaultPaywallFontProvider(), - purchaseStarted: PurchaseStartedHandler? = nil, + purchaseStarted: @escaping PurchaseStartedHandler, purchaseCompleted: PurchaseOrRestoreCompletedHandler? = nil, purchaseCancelled: PurchaseCancelledHandler? = nil, restoreCompleted: PurchaseOrRestoreCompletedHandler? = nil, purchaseFailure: PurchaseFailureHandler? = nil, restoreFailure: PurchaseFailureHandler? = nil + ) -> some View { + return self.paywallFooter( + condensed: condensed, + fonts: fonts, + purchaseStarted: { _ in + purchaseStarted() + }, + purchaseCompleted: purchaseCompleted, + purchaseCancelled: purchaseCancelled, + restoreStarted: nil, + restoreCompleted: restoreCompleted, + purchaseFailure: purchaseFailure, + restoreFailure: restoreFailure + ) + } + + /// Presents a ``PaywallFooterView`` at the bottom of a view that loads the `Offerings.current`. + /// ```swift + /// var body: some View { + /// YourPaywall() + /// .paywallFooter() + /// } + /// ``` + /// + /// ### Related Articles + /// [Documentation](https://rev.cat/paywalls) + public func paywallFooter( + condensed: Bool = false, + fonts: PaywallFontProvider = DefaultPaywallFontProvider(), + purchaseStarted: PurchaseOfPackageStartedHandler? = nil, + purchaseCompleted: PurchaseOrRestoreCompletedHandler? = nil, + purchaseCancelled: PurchaseCancelledHandler? = nil, + restoreStarted: RestoreStartedHandler? = nil, + restoreCompleted: PurchaseOrRestoreCompletedHandler? = nil, + purchaseFailure: PurchaseFailureHandler? = nil, + restoreFailure: PurchaseFailureHandler? = nil ) -> some View { return self.paywallFooter( offering: nil, @@ -54,6 +97,52 @@ extension View { ) } + // swiftlint:disable line_length + /// Presents a ``PaywallFooterView`` at the bottom of a view with the given offering. + /// ```swift + /// var body: some View { + /// YourPaywall() + /// .paywallFooter(offering: offering) + /// } + /// ``` + /// + /// ### Related Articles + /// [Documentation](https://rev.cat/paywalls) + @available(iOS, deprecated: 1, renamed: "paywallFooter(offering:condensed:fonts:purchaseStarted:purchaseCompleted:purchaseCancelled:restoreStarted:restoreCompleted:purchaseFailure:restoreFailure:)") + @available(tvOS, deprecated: 1, renamed: "paywallFooter(offering:condensed:fonts:purchaseStarted:purchaseCompleted:purchaseCancelled:restoreStarted:restoreCompleted:purchaseFailure:restoreFailure:)") + @available(watchOS, deprecated: 1, renamed: "paywallFooter(offering:condensed:fonts:purchaseStarted:purchaseCompleted:purchaseCancelled:restoreStarted:restoreCompleted:purchaseFailure:restoreFailure:)") + @available(macOS, deprecated: 1, renamed: "paywallFooter(offering:condensed:fonts:purchaseStarted:purchaseCompleted:purchaseCancelled:restoreStarted:restoreCompleted:purchaseFailure:restoreFailure:)") + @available(macCatalyst, deprecated: 1, renamed: "paywallFooter(offering:condensed:fonts:purchaseStarted:purchaseCompleted:purchaseCancelled:restoreStarted:restoreCompleted:purchaseFailure:restoreFailure:)") + // swiftlint:enable line_length + public func paywallFooter( + offering: Offering, + condensed: Bool = false, + fonts: PaywallFontProvider = DefaultPaywallFontProvider(), + purchaseStarted: @escaping PurchaseStartedHandler, + purchaseCompleted: PurchaseOrRestoreCompletedHandler? = nil, + purchaseCancelled: PurchaseCancelledHandler? = nil, + restoreCompleted: PurchaseOrRestoreCompletedHandler? = nil, + purchaseFailure: PurchaseFailureHandler? = nil, + restoreFailure: PurchaseFailureHandler? = nil + ) -> some View { + return self.paywallFooter( + offering: offering, + customerInfo: nil, + condensed: condensed, + fonts: fonts, + introEligibility: nil, + purchaseStarted: { _ in + purchaseStarted() + }, + purchaseCompleted: purchaseCompleted, + purchaseCancelled: purchaseCancelled, + restoreStarted: nil, + restoreCompleted: restoreCompleted, + purchaseFailure: purchaseFailure, + restoreFailure: restoreFailure + ) + } + /// Presents a ``PaywallFooterView`` at the bottom of a view with the given offering. /// ```swift /// var body: some View { @@ -68,9 +157,10 @@ extension View { offering: Offering, condensed: Bool = false, fonts: PaywallFontProvider = DefaultPaywallFontProvider(), - purchaseStarted: PurchaseStartedHandler? = nil, + purchaseStarted: PurchaseOfPackageStartedHandler? = nil, purchaseCompleted: PurchaseOrRestoreCompletedHandler? = nil, purchaseCancelled: PurchaseCancelledHandler? = nil, + restoreStarted: RestoreStartedHandler? = nil, restoreCompleted: PurchaseOrRestoreCompletedHandler? = nil, purchaseFailure: PurchaseFailureHandler? = nil, restoreFailure: PurchaseFailureHandler? = nil @@ -98,7 +188,7 @@ extension View { fonts: PaywallFontProvider = DefaultPaywallFontProvider(), introEligibility: TrialOrIntroEligibilityChecker? = nil, purchaseHandler: PurchaseHandler? = nil, - purchaseStarted: PurchaseStartedHandler? = nil, + purchaseStarted: PurchaseOfPackageStartedHandler? = nil, purchaseCompleted: PurchaseOrRestoreCompletedHandler? = nil, purchaseCancelled: PurchaseCancelledHandler? = nil, restoreStarted: RestoreStartedHandler? = nil, @@ -135,7 +225,7 @@ private struct PresentingPaywallFooterModifier: ViewModifier { let configuration: PaywallViewConfiguration - let purchaseStarted: PurchaseStartedHandler? + let purchaseStarted: PurchaseOfPackageStartedHandler? let purchaseCompleted: PurchaseOrRestoreCompletedHandler? let purchaseCancelled: PurchaseCancelledHandler? let purchaseFailure: PurchaseFailureHandler? @@ -149,7 +239,7 @@ private struct PresentingPaywallFooterModifier: ViewModifier { .safeAreaInset(edge: .bottom) { PaywallView(configuration: self.configuration) .onPurchaseStarted { - self.purchaseStarted?() + self.purchaseStarted?($0) } .onPurchaseCompleted { self.purchaseCompleted?($0) diff --git a/RevenueCatUI/View+PurchaseRestoreCompleted.swift b/RevenueCatUI/View+PurchaseRestoreCompleted.swift index ab1b1f73f1..9cedf28c0f 100644 --- a/RevenueCatUI/View+PurchaseRestoreCompleted.swift +++ b/RevenueCatUI/View+PurchaseRestoreCompleted.swift @@ -22,8 +22,16 @@ public typealias PurchaseCompletedHandler = @MainActor @Sendable (_ transaction: _ customerInfo: CustomerInfo) -> Void /// A closure used for notifying of purchase initiation. +@available(iOS, deprecated: 1, renamed: "PurchaseOfPackageStartedHandler") +@available(tvOS, deprecated: 1, renamed: "PurchaseOfPackageStartedHandler") +@available(watchOS, deprecated: 1, renamed: "PurchaseOfPackageStartedHandler") +@available(macOS, deprecated: 1, renamed: "PurchaseOfPackageStartedHandler") +@available(macCatalyst, deprecated: 1, renamed: "PurchaseOfPackageStartedHandler") public typealias PurchaseStartedHandler = @MainActor @Sendable () -> Void +/// A closure used for notifying of purchase of a package initiation. +public typealias PurchaseOfPackageStartedHandler = @MainActor @Sendable (_ package: Package) -> Void + /// A closure used for notifying of purchase cancellation. public typealias PurchaseCancelledHandler = @MainActor @Sendable () -> Void @@ -51,8 +59,35 @@ extension View { /// /// ### Related Articles /// [Documentation](https://rev.cat/paywalls) + @available(iOS, deprecated: 1, renamed: "onPurchaseStarted(handler:)") + @available(tvOS, deprecated: 1, renamed: "onPurchaseStarted(handler:)") + @available(watchOS, deprecated: 1, renamed: "onPurchaseStarted(handler:)") + @available(macOS, deprecated: 1, renamed: "onPurchaseStarted(handler:)") + @available(macCatalyst, deprecated: 1, renamed: "onPurchaseStarted(handler:)") public func onPurchaseStarted( _ handler: @escaping PurchaseStartedHandler + ) -> some View { + return self.modifier(OnPurchaseStartedModifier(handler: { _ in + handler() + })) + } + + /// Invokes the given closure when a purchase of a package begins. + /// Example: + /// ```swift + /// @State + /// var body: some View { + /// PaywallView() + /// .onPurchaseStarted { package in + /// print("Purchase started for package: \(package)") + /// } + /// } + /// ``` + /// + /// ### Related Articles + /// [Documentation](https://rev.cat/paywalls) + public func onPurchaseStarted( + _ handler: @escaping PurchaseOfPackageStartedHandler ) -> some View { return self.modifier(OnPurchaseStartedModifier(handler: handler)) } @@ -221,13 +256,13 @@ extension View { @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) private struct OnPurchaseStartedModifier: ViewModifier { - let handler: PurchaseStartedHandler + let handler: PurchaseOfPackageStartedHandler func body(content: Content) -> some View { content - .onPreferenceChange(PurchasedInProgressPreferenceKey.self) { inProgress in - if inProgress { - self.handler() + .onPreferenceChange(PurchaseInProgressPreferenceKey.self) { package in + if let package { + self.handler(package) } } } diff --git a/Tests/APITesters/RevenueCatUIAPITester/SwiftAPITester/PaywallViewAPI.swift b/Tests/APITesters/RevenueCatUIAPITester/SwiftAPITester/PaywallViewAPI.swift index 5db314334e..909c012e89 100644 --- a/Tests/APITesters/RevenueCatUIAPITester/SwiftAPITester/PaywallViewAPI.swift +++ b/Tests/APITesters/RevenueCatUIAPITester/SwiftAPITester/PaywallViewAPI.swift @@ -15,7 +15,9 @@ struct App: View { private var offering: Offering private var fonts: PaywallFontProvider private var purchaseOrRestoreCompleted: PurchaseOrRestoreCompletedHandler = { (_: CustomerInfo) in } + @available(*, deprecated) // Ignore deprecation warnings private var purchaseStarted: PurchaseStartedHandler = { } + private var purchaseOfPackageStarted: PurchaseOfPackageStartedHandler = { (_: Package) in } private var purchaseCompleted: PurchaseCompletedHandler = { (_: StoreTransaction?, _: CustomerInfo) in } private var purchaseCancelled: PurchaseCancelledHandler = { () in } private var restoreStarted: RestoreStartedHandler = { } @@ -41,7 +43,41 @@ struct App: View { } @ViewBuilder - var checkPresentPaywallIfNeeded: some View { + @available(*, deprecated) // Ignore deprecation warnings + var checkDeprecatedPresentPaywallIfNeededWithRequiredEntitlement: some View { + Text("") + .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", + purchaseStarted: self.purchaseStarted) + .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", + fonts: self.fonts, + purchaseStarted: self.purchaseStarted) + .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", + fonts: self.fonts, + purchaseStarted: self.purchaseStarted, + purchaseCompleted: self.purchaseOrRestoreCompleted, + restoreCompleted: self.purchaseOrRestoreCompleted, + onDismiss: self.paywallDismissed) + .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", + offering: self.offering, + fonts: self.fonts, + purchaseStarted: self.purchaseStarted, + purchaseCompleted: self.purchaseOrRestoreCompleted, + purchaseCancelled: self.purchaseCancelled, + restoreCompleted: self.purchaseOrRestoreCompleted, + onDismiss: self.paywallDismissed) + .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", + offering: self.offering, + fonts: self.fonts, + purchaseStarted: self.purchaseStarted, + purchaseCompleted: self.purchaseOrRestoreCompleted, + purchaseCancelled: self.purchaseCancelled, + restoreCompleted: self.purchaseOrRestoreCompleted, + purchaseFailure: self.failureHandler, + onDismiss: self.paywallDismissed) + } + + @ViewBuilder + var checkPresentPaywallIfNeededWithRequiredEntitlement: some View { Text("") .presentPaywallIfNeeded(requiredEntitlementIdentifier: "") .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", offering: nil) @@ -49,12 +85,12 @@ struct App: View { .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", fonts: self.fonts) .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", presentationMode: .sheet) .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", presentationMode: .fullScreen) - .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", - purchaseStarted: self.purchaseStarted) .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", purchaseCompleted: self.purchaseOrRestoreCompleted) .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", purchaseCancelled: self.purchaseCancelled) + .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", + restoreStarted: self.restoreStarted) .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", restoreCompleted: self.purchaseOrRestoreCompleted) .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", @@ -66,9 +102,6 @@ struct App: View { .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", offering: self.offering, fonts: self.fonts) - .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", - fonts: self.fonts, - purchaseStarted: self.purchaseStarted) .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", fonts: self.fonts, purchaseStarted: nil) @@ -78,6 +111,9 @@ struct App: View { .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", fonts: self.fonts, purchaseCancelled: self.purchaseCancelled) + .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", + fonts: self.fonts, + restoreStarted: self.restoreStarted) .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", fonts: self.fonts, restoreCompleted: self.purchaseOrRestoreCompleted) @@ -100,12 +136,6 @@ struct App: View { purchaseCompleted: self.purchaseOrRestoreCompleted, restoreCompleted: self.purchaseOrRestoreCompleted, onDismiss: self.paywallDismissed) - .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", - fonts: self.fonts, - purchaseStarted: self.purchaseStarted, - purchaseCompleted: self.purchaseOrRestoreCompleted, - restoreCompleted: self.purchaseOrRestoreCompleted, - onDismiss: self.paywallDismissed) .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", fonts: self.fonts, purchaseStarted: nil, @@ -121,17 +151,10 @@ struct App: View { .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", offering: self.offering, fonts: self.fonts, - purchaseStarted: self.purchaseStarted, - purchaseCompleted: self.purchaseOrRestoreCompleted, - purchaseCancelled: self.purchaseCancelled, - restoreCompleted: self.purchaseOrRestoreCompleted, - onDismiss: self.paywallDismissed) - .presentPaywallIfNeeded(requiredEntitlementIdentifier: "", - offering: self.offering, - fonts: self.fonts, - purchaseStarted: self.purchaseStarted, + purchaseStarted: self.purchaseOfPackageStarted, purchaseCompleted: self.purchaseOrRestoreCompleted, purchaseCancelled: self.purchaseCancelled, + restoreStarted: self.restoreStarted, restoreCompleted: self.purchaseOrRestoreCompleted, purchaseFailure: self.failureHandler, onDismiss: self.paywallDismissed) @@ -142,26 +165,30 @@ struct App: View { purchaseStarted: nil, purchaseCompleted: nil, purchaseCancelled: nil, + restoreStarted: nil, restoreCompleted: nil, purchaseFailure: nil, restoreFailure: nil, onDismiss: nil) - .presentPaywallIfNeeded(offering: nil) { (_: CustomerInfo) in false } - .presentPaywallIfNeeded(offering: self.offering) { (_: CustomerInfo) in false } - .presentPaywallIfNeeded(fonts: self.fonts) { (_: CustomerInfo) in false } - .presentPaywallIfNeeded(offering: self.offering, fonts: self.fonts) { (_: CustomerInfo) in false } + } + + @ViewBuilder + @available(*, deprecated) // Ignore deprecation warnings + var checkDeprecatedPresentPaywallIfNeeded: some View { + Text("") .presentPaywallIfNeeded(fonts: self.fonts) { (_: CustomerInfo) in false - } purchaseCompleted: { - self.purchaseOrRestoreCompleted($0) - } - .presentPaywallIfNeeded(presentationMode: .sheet) { (_: CustomerInfo) in - false + } purchaseStarted: { + self.purchaseStarted() } .presentPaywallIfNeeded(fonts: self.fonts) { (_: CustomerInfo) in false } purchaseStarted: { self.purchaseStarted() + } purchaseCompleted: { + self.purchaseOrRestoreCompleted($0) + } restoreCompleted: { + self.purchaseOrRestoreCompleted($0) } .presentPaywallIfNeeded(fonts: self.fonts) { (_: CustomerInfo) in false @@ -171,8 +198,10 @@ struct App: View { self.purchaseOrRestoreCompleted($0) } restoreCompleted: { self.purchaseOrRestoreCompleted($0) + } onDismiss: { + self.paywallDismissed() } - .presentPaywallIfNeeded(fonts: self.fonts) { (_: CustomerInfo) in + .presentPaywallIfNeeded(offering: self.offering, fonts: self.fonts) { (_: CustomerInfo) in false } purchaseStarted: { self.purchaseStarted() @@ -189,11 +218,34 @@ struct App: View { self.purchaseStarted() } purchaseCompleted: { self.purchaseOrRestoreCompleted($0) + } purchaseCancelled: { + self.purchaseCancelled() } restoreCompleted: { self.purchaseOrRestoreCompleted($0) + } purchaseFailure: { + self.failureHandler($0) + } restoreFailure: { + self.failureHandler($0) } onDismiss: { self.paywallDismissed() } + } + + @ViewBuilder + var checkPresentPaywallIfNeeded: some View { + Text("") + .presentPaywallIfNeeded(offering: nil) { (_: CustomerInfo) in false } + .presentPaywallIfNeeded(offering: self.offering) { (_: CustomerInfo) in false } + .presentPaywallIfNeeded(fonts: self.fonts) { (_: CustomerInfo) in false } + .presentPaywallIfNeeded(offering: self.offering, fonts: self.fonts) { (_: CustomerInfo) in false } + .presentPaywallIfNeeded(fonts: self.fonts) { (_: CustomerInfo) in + false + } purchaseCompleted: { + self.purchaseOrRestoreCompleted($0) + } + .presentPaywallIfNeeded(presentationMode: .sheet) { (_: CustomerInfo) in + false + } .presentPaywallIfNeeded(offering: self.offering, fonts: self.fonts) { (_: CustomerInfo) in false } purchaseCompleted: { @@ -208,11 +260,13 @@ struct App: View { .presentPaywallIfNeeded(offering: self.offering, fonts: self.fonts) { (_: CustomerInfo) in false } purchaseStarted: { - self.purchaseStarted() + self.purchaseOfPackageStarted($0) } purchaseCompleted: { self.purchaseOrRestoreCompleted($0) } purchaseCancelled: { self.purchaseCancelled() + } restoreStarted: { + self.restoreStarted() } restoreCompleted: { self.purchaseOrRestoreCompleted($0) } purchaseFailure: { @@ -225,46 +279,121 @@ struct App: View { .presentPaywallIfNeeded(offering: nil, fonts: self.fonts, presentationMode: .fullScreen, - shouldDisplay: { (_: CustomerInfo) in - false - }, + shouldDisplay: { (_: CustomerInfo) in false }, purchaseStarted: nil, purchaseCompleted: nil, purchaseCancelled: nil, + restoreStarted: nil, restoreCompleted: nil, purchaseFailure: nil, restoreFailure: nil, onDismiss: nil) } + @available(*, deprecated) // Ignore deprecation warnings @ViewBuilder - var checkPaywallFooter: some View { + var checkDeprecatedPaywallFooter: some View { Text("") - .paywallFooter() - .paywallFooter(purchaseStarted: self.purchaseStarted) - .paywallFooter(purchaseCompleted: self.purchaseOrRestoreCompleted) - .paywallFooter(purchaseCancelled: self.purchaseCancelled) - .paywallFooter(restoreCompleted: self.purchaseOrRestoreCompleted) - .paywallFooter(purchaseFailure: self.failureHandler) - .paywallFooter(restoreFailure: self.failureHandler) + .paywallFooter(condensed: true, + purchaseStarted: self.purchaseStarted) + .paywallFooter(condensed: true, + fonts: self.fonts, + purchaseStarted: self.purchaseStarted, + purchaseCompleted: self.purchaseOrRestoreCompleted, + restoreCompleted: self.purchaseOrRestoreCompleted, + purchaseFailure: self.failureHandler, + restoreFailure: self.failureHandler) .paywallFooter(purchaseStarted: self.purchaseStarted, purchaseCompleted: self.purchaseOrRestoreCompleted, purchaseCancelled: self.purchaseCancelled, restoreCompleted: self.purchaseOrRestoreCompleted, purchaseFailure: self.failureHandler, restoreFailure: self.failureHandler) - .paywallFooter(fonts: self.fonts) + .paywallFooter(condensed: true, + fonts: self.fonts, + purchaseStarted: self.purchaseStarted) + .paywallFooter(condensed: true, + fonts: self.fonts, + purchaseStarted: self.purchaseStarted, + purchaseCompleted: self.purchaseOrRestoreCompleted) + .paywallFooter(condensed: true, + purchaseStarted: self.purchaseStarted, + purchaseCompleted: self.purchaseOrRestoreCompleted) + .paywallFooter(condensed: true, + fonts: self.fonts, + purchaseStarted: self.purchaseStarted, + purchaseCompleted: self.purchaseOrRestoreCompleted, + purchaseCancelled: self.purchaseCancelled, + restoreCompleted: self.purchaseOrRestoreCompleted, + purchaseFailure: self.failureHandler, + restoreFailure: self.failureHandler) + .paywallFooter(offering: offering, purchaseStarted: self.purchaseStarted) + .paywallFooter(offering: offering, + fonts: self.fonts, + purchaseStarted: self.purchaseStarted, + purchaseCompleted: self.purchaseOrRestoreCompleted) + .paywallFooter(offering: offering, + condensed: true, + purchaseStarted: self.purchaseStarted) + .paywallFooter(offering: offering, + condensed: true, + purchaseStarted: self.purchaseStarted, + purchaseCompleted: self.purchaseOrRestoreCompleted, + purchaseCancelled: self.purchaseCancelled, + restoreCompleted: self.purchaseOrRestoreCompleted, + purchaseFailure: self.failureHandler, + restoreFailure: self.failureHandler) + .paywallFooter(offering: offering, + purchaseStarted: self.purchaseStarted) + .paywallFooter(offering: offering, + fonts: self.fonts, + purchaseStarted: self.purchaseStarted) + .paywallFooter(offering: offering, + fonts: self.fonts, + purchaseStarted: self.purchaseStarted, + purchaseCompleted: self.purchaseOrRestoreCompleted, + purchaseCancelled: self.purchaseCancelled, + restoreCompleted: self.purchaseOrRestoreCompleted, + purchaseFailure: self.failureHandler, + restoreFailure: self.failureHandler) + .paywallFooter(offering: offering, + condensed: true, + fonts: self.fonts, + purchaseStarted: self.purchaseStarted) + .paywallFooter(offering: offering, + condensed: true, + fonts: self.fonts, + purchaseStarted: self.purchaseStarted, + purchaseCompleted: self.purchaseOrRestoreCompleted, + restoreCompleted: self.purchaseOrRestoreCompleted) + .paywallFooter(offering: offering, + condensed: true, + fonts: self.fonts, + purchaseStarted: self.purchaseStarted, + purchaseCompleted: self.purchaseOrRestoreCompleted, + purchaseCancelled: self.purchaseCancelled, + restoreCompleted: self.purchaseOrRestoreCompleted, + purchaseFailure: self.failureHandler, + restoreFailure: self.failureHandler) + } + + @ViewBuilder + var checkPaywallFooter: some View { + Text("") + .paywallFooter() + .paywallFooter(purchaseStarted: self.purchaseOfPackageStarted) .paywallFooter(purchaseCompleted: self.purchaseOrRestoreCompleted) + .paywallFooter(purchaseCancelled: self.purchaseCancelled) + .paywallFooter(restoreCompleted: self.purchaseOrRestoreCompleted) + .paywallFooter(purchaseFailure: self.failureHandler) + .paywallFooter(restoreFailure: self.failureHandler) + .paywallFooter(fonts: self.fonts) .paywallFooter(fonts: self.fonts, purchaseCompleted: self.purchaseOrRestoreCompleted) .paywallFooter(condensed: true) .paywallFooter(condensed: true, fonts: self.fonts) - .paywallFooter(condensed: true, purchaseStarted: self.purchaseStarted) .paywallFooter(condensed: true, purchaseStarted: nil) .paywallFooter(condensed: true, purchaseCompleted: self.purchaseOrRestoreCompleted) - .paywallFooter(condensed: true, - purchaseStarted: self.purchaseStarted, - purchaseCompleted: self.purchaseOrRestoreCompleted) .paywallFooter(condensed: true, purchaseStarted: nil, purchaseCompleted: self.purchaseOrRestoreCompleted) @@ -273,23 +402,37 @@ struct App: View { restoreCompleted: self.purchaseOrRestoreCompleted) .paywallFooter(condensed: true, fonts: self.fonts, - purchaseStarted: self.purchaseStarted) + purchaseStarted: self.purchaseOfPackageStarted) .paywallFooter(condensed: true, fonts: self.fonts, purchaseCompleted: self.purchaseOrRestoreCompleted) .paywallFooter(condensed: true, fonts: self.fonts, - purchaseStarted: self.purchaseStarted, - restoreCompleted: self.purchaseOrRestoreCompleted) + purchaseStarted: self.purchaseOfPackageStarted, + purchaseCompleted: self.purchaseOrRestoreCompleted) .paywallFooter(condensed: true, + fonts: self.fonts, + purchaseStarted: self.purchaseOfPackageStarted, + purchaseCompleted: self.purchaseOrRestoreCompleted, + purchaseCancelled: self.purchaseCancelled, + restoreCompleted: self.purchaseOrRestoreCompleted, + purchaseFailure: self.failureHandler, + restoreFailure: self.failureHandler) + .paywallFooter(offering: offering) + .paywallFooter(offering: offering, fonts: self.fonts) + .paywallFooter(offering: offering, purchaseCompleted: self.purchaseOrRestoreCompleted) + .paywallFooter(offering: offering, restoreCompleted: self.purchaseOrRestoreCompleted) + .paywallFooter(offering: offering, fonts: self.fonts, purchaseCompleted: self.purchaseOrRestoreCompleted) + .paywallFooter(offering: offering, fonts: self.fonts, purchaseCompleted: self.purchaseOrRestoreCompleted, restoreCompleted: self.purchaseOrRestoreCompleted) - .paywallFooter(condensed: true, + .paywallFooter(offering: offering, fonts: self.fonts, - purchaseStarted: self.purchaseStarted, + purchaseStarted: self.purchaseOfPackageStarted, purchaseCompleted: self.purchaseOrRestoreCompleted, purchaseCancelled: self.purchaseCancelled, + restoreStarted: self.restoreStarted, restoreCompleted: self.purchaseOrRestoreCompleted, purchaseFailure: self.failureHandler, restoreFailure: self.failureHandler) @@ -298,6 +441,7 @@ struct App: View { purchaseStarted: nil, purchaseCompleted: nil, purchaseCancelled: nil, + restoreStarted: nil, restoreCompleted: nil, purchaseFailure: nil, restoreFailure: nil) @@ -307,9 +451,6 @@ struct App: View { .paywallFooter(offering: offering, condensed: true, fonts: self.fonts) - .paywallFooter(offering: offering, - condensed: true, - purchaseStarted: self.purchaseStarted) .paywallFooter(offering: offering, condensed: true, purchaseCompleted: self.purchaseOrRestoreCompleted) @@ -318,20 +459,21 @@ struct App: View { restoreCompleted: self.purchaseOrRestoreCompleted) .paywallFooter(offering: offering, condensed: true, - purchaseStarted: self.purchaseStarted, + purchaseStarted: self.purchaseOfPackageStarted, purchaseCompleted: self.purchaseOrRestoreCompleted, purchaseCancelled: self.purchaseCancelled, + restoreStarted: self.restoreStarted, restoreCompleted: self.purchaseOrRestoreCompleted, purchaseFailure: self.failureHandler, restoreFailure: self.failureHandler) .paywallFooter(offering: offering, fonts: self.fonts) - .paywallFooter(offering: offering, - purchaseStarted: self.purchaseStarted) .paywallFooter(offering: offering, purchaseCompleted: self.purchaseOrRestoreCompleted) .paywallFooter(offering: offering, purchaseCancelled: self.purchaseCancelled) + .paywallFooter(offering: offering, + restoreStarted: self.restoreStarted) .paywallFooter(offering: offering, restoreCompleted: self.purchaseOrRestoreCompleted) .paywallFooter(offering: offering, @@ -341,9 +483,6 @@ struct App: View { .paywallFooter(offering: offering, fonts: self.fonts, purchaseCompleted: self.purchaseOrRestoreCompleted) - .paywallFooter(offering: offering, - fonts: self.fonts, - purchaseStarted: self.purchaseStarted) .paywallFooter(offering: offering, fonts: self.fonts, purchaseCompleted: self.purchaseOrRestoreCompleted, @@ -354,28 +493,38 @@ struct App: View { restoreCompleted: self.purchaseOrRestoreCompleted) .paywallFooter(offering: offering, fonts: self.fonts, - purchaseStarted: self.purchaseStarted, + purchaseStarted: self.purchaseOfPackageStarted, purchaseCompleted: self.purchaseOrRestoreCompleted, purchaseCancelled: self.purchaseCancelled, + restoreStarted: self.restoreStarted, restoreCompleted: self.purchaseOrRestoreCompleted, purchaseFailure: self.failureHandler, restoreFailure: self.failureHandler) - .paywallFooter(offering: offering, condensed: true, fonts: self.fonts, - purchaseCompleted: self.purchaseOrRestoreCompleted) + purchaseStarted: self.purchaseOfPackageStarted) .paywallFooter(offering: offering, condensed: true, fonts: self.fonts, + purchaseStarted: self.purchaseOfPackageStarted, purchaseCompleted: self.purchaseOrRestoreCompleted, restoreCompleted: self.purchaseOrRestoreCompleted) .paywallFooter(offering: offering, condensed: true, fonts: self.fonts, - purchaseStarted: self.purchaseStarted, + purchaseCompleted: self.purchaseOrRestoreCompleted) + .paywallFooter(offering: offering, + condensed: true, + fonts: self.fonts, + purchaseCompleted: self.purchaseOrRestoreCompleted, + restoreCompleted: self.purchaseOrRestoreCompleted) + .paywallFooter(offering: offering, + fonts: self.fonts, + purchaseStarted: self.purchaseOfPackageStarted, purchaseCompleted: self.purchaseOrRestoreCompleted, purchaseCancelled: self.purchaseCancelled, + restoreStarted: self.restoreStarted, restoreCompleted: self.purchaseOrRestoreCompleted, purchaseFailure: self.failureHandler, restoreFailure: self.failureHandler) @@ -385,15 +534,23 @@ struct App: View { purchaseStarted: nil, purchaseCompleted: nil, purchaseCancelled: nil, + restoreStarted: nil, restoreCompleted: nil, purchaseFailure: nil, restoreFailure: nil) } @ViewBuilder - var checkOnPurchaseAndRestoreCompleted: some View { + @available(*, deprecated) // Ignore deprecation warnings + var checkDeprecatedPaywallViewModifiers: some View { Text("") .onPurchaseStarted(self.purchaseStarted) + } + + @ViewBuilder + var checkPaywallViewModifiers: some View { + Text("") + .onPurchaseStarted(self.purchaseOfPackageStarted) .onPurchaseCompleted(self.purchaseOrRestoreCompleted) .onPurchaseCompleted(self.purchaseCompleted) .onPurchaseCancelled(self.purchaseCancelled) diff --git a/Tests/RevenueCatUITests/PaywallFooterTests.swift b/Tests/RevenueCatUITests/PaywallFooterTests.swift index 6769ef6988..9f06791fa6 100644 --- a/Tests/RevenueCatUITests/PaywallFooterTests.swift +++ b/Tests/RevenueCatUITests/PaywallFooterTests.swift @@ -30,7 +30,7 @@ class PaywallFooterTests: TestCase { } func testPresentWithPurchaseStarted() throws { - var started = false + var packageBeingPurchased: Package? try Text("") .paywallFooter( @@ -38,7 +38,7 @@ class PaywallFooterTests: TestCase { customerInfo: TestData.customerInfo, introEligibility: .producing(eligibility: .eligible), purchaseHandler: Self.purchaseHandler, - purchaseStarted: { started = true } + purchaseStarted: { package in packageBeingPurchased = package } ) .addToHierarchy() @@ -46,7 +46,7 @@ class PaywallFooterTests: TestCase { _ = try await Self.purchaseHandler.purchase(package: Self.package) } - expect(started).toEventually(beTrue()) + expect(packageBeingPurchased).toEventually(be(Self.package)) } func testPresentWithPurchaseHandler() throws { diff --git a/Tests/RevenueCatUITests/PresentIfNeededTests.swift b/Tests/RevenueCatUITests/PresentIfNeededTests.swift index ce4e900338..40b17b5aa6 100644 --- a/Tests/RevenueCatUITests/PresentIfNeededTests.swift +++ b/Tests/RevenueCatUITests/PresentIfNeededTests.swift @@ -33,15 +33,15 @@ class PresentIfNeededTests: TestCase { self.continueAfterFailure = false let handler = Self.purchaseHandler.with(delay: 3) - var started = false + var packageBeingPurchased: Package? let dispose = try Text("") .presentPaywallIfNeeded(offering: Self.offering, introEligibility: .producing(eligibility: .eligible), purchaseHandler: handler) { _ in return true - } purchaseStarted: { - started = true + } purchaseStarted: { aPackage in + packageBeingPurchased = aPackage } customerInfoFetcher: { return TestData.customerInfo } @@ -55,7 +55,7 @@ class PresentIfNeededTests: TestCase { dispose() } - expect(started).toEventually(beTrue()) + expect(packageBeingPurchased).toEventuallyNot(beNil()) task.cancel() } diff --git a/Tests/RevenueCatUITests/PurchaseCompletedHandlerTests.swift b/Tests/RevenueCatUITests/PurchaseCompletedHandlerTests.swift index cbe4b0d7bc..26bfe3cb61 100644 --- a/Tests/RevenueCatUITests/PurchaseCompletedHandlerTests.swift +++ b/Tests/RevenueCatUITests/PurchaseCompletedHandlerTests.swift @@ -25,6 +25,7 @@ class PurchaseCompletedHandlerTests: TestCase { func testOnPurchaseStarted() throws { var started = false + var packageBeingPurchased: Package? try PaywallView( offering: Self.offering.withLocalImages, @@ -35,6 +36,9 @@ class PurchaseCompletedHandlerTests: TestCase { .onPurchaseStarted { started = true } + .onPurchaseStarted { package in + packageBeingPurchased = package + } .addToHierarchy() Task { @@ -42,6 +46,7 @@ class PurchaseCompletedHandlerTests: TestCase { } expect(started).toEventually(beTrue()) + expect(packageBeingPurchased).toEventuallyNot(beNil()) } func testOnPurchaseCompletedWithCancellation() throws { diff --git a/Tests/RevenueCatUITests/Purchasing/PurchaseHandlerTests.swift b/Tests/RevenueCatUITests/Purchasing/PurchaseHandlerTests.swift index 5b0b01a084..03c0b28ef2 100644 --- a/Tests/RevenueCatUITests/Purchasing/PurchaseHandlerTests.swift +++ b/Tests/RevenueCatUITests/Purchasing/PurchaseHandlerTests.swift @@ -28,7 +28,7 @@ class PurchaseHandlerTests: TestCase { expect(handler.purchaseResult).to(beNil()) expect(handler.restoredCustomerInfo).to(beNil()) expect(handler.purchased) == false - expect(handler.purchaseInProgress) == false + expect(handler.packageBeingPurchased).to(beNil()) expect(handler.restoreInProgress) == false expect(handler.actionInProgress) == false expect(handler.purchaseError).to(beNil()) @@ -44,7 +44,7 @@ class PurchaseHandlerTests: TestCase { expect(handler.purchaseResult?.userCancelled) == false expect(handler.restoredCustomerInfo).to(beNil()) expect(handler.purchased) == true - expect(handler.purchaseInProgress) == false + expect(handler.packageBeingPurchased).to(beNil()) expect(handler.restoreInProgress) == false expect(handler.actionInProgress) == false } @@ -56,7 +56,7 @@ class PurchaseHandlerTests: TestCase { expect(handler.purchaseResult?.userCancelled) == true expect(handler.purchaseResult?.customerInfo) === TestData.customerInfo expect(handler.purchased) == false - expect(handler.purchaseInProgress) == false + expect(handler.packageBeingPurchased).to(beNil()) expect(handler.restoreInProgress) == false expect(handler.actionInProgress) == false } @@ -75,7 +75,7 @@ class PurchaseHandlerTests: TestCase { expect(handler.purchaseResult).to(beNil()) expect(handler.purchased) == false - expect(handler.purchaseInProgress) == false + expect(handler.packageBeingPurchased).to(beNil()) expect(handler.restoreInProgress) == false expect(handler.actionInProgress) == false expect(handler.purchaseError).to(matchError(error)) @@ -93,10 +93,10 @@ class PurchaseHandlerTests: TestCase { } try await asyncWait { - handler.actionInProgress && handler.purchaseInProgress + handler.actionInProgress && handler.packageBeingPurchased != nil } - expect(handler.purchaseInProgress) == true + expect(handler.packageBeingPurchased) == TestData.packageWithIntroOffer expect(handler.actionInProgress) == true expect(handler.restoreInProgress) == false @@ -106,7 +106,7 @@ class PurchaseHandlerTests: TestCase { // Wait for purchase task to complete _ = try await task.value - expect(handler.purchaseInProgress) == false + expect(handler.packageBeingPurchased).to(beNil()) expect(handler.actionInProgress) == false } @@ -125,10 +125,10 @@ class PurchaseHandlerTests: TestCase { } expect(handler.actionInProgress) == true - expect(handler.purchaseInProgress) == false + expect(handler.packageBeingPurchased).to(beNil()) expect(handler.restoreInProgress) == true - // Finish rewstore + // Finish restore try asyncHandler.resume() // Wait for restore task to complete @@ -145,7 +145,7 @@ class PurchaseHandlerTests: TestCase { expect(result.success) == false expect(handler.restoredCustomerInfo).to(beNil()) expect(handler.purchaseResult).to(beNil()) - expect(handler.purchaseInProgress) == false + expect(handler.packageBeingPurchased).to(beNil()) expect(handler.actionInProgress) == false expect(handler.restoreInProgress) == false @@ -153,7 +153,7 @@ class PurchaseHandlerTests: TestCase { expect(handler.restoredCustomerInfo) === TestData.customerInfo expect(handler.purchaseResult).to(beNil()) - expect(handler.purchaseInProgress) == false + expect(handler.packageBeingPurchased).to(beNil()) expect(handler.actionInProgress) == false expect(handler.restoreInProgress) == false } @@ -186,7 +186,7 @@ class PurchaseHandlerTests: TestCase { } expect(handler.purchaseResult).to(beNil()) expect(handler.purchased) == false - expect(handler.purchaseInProgress) == false + expect(handler.packageBeingPurchased).to(beNil()) expect(handler.actionInProgress) == false expect(handler.restoreInProgress) == false expect(handler.restoreError).to(matchError(error))