mirror of
https://github.com/RevenueCat/purchases-ios
synced 2026-11-11 22:55:55 +05:45
* feat: add `hasPaywall` getter to `Offering` Returns `true` if the offering has either a v1 or a v2 paywall * docs: update some methods references changing `PaywallData` with "paywall", as `PaywallData` only refers to v1 paywalls * docs: improve reference of `Offering`'s `paywall` and `paywallComponents` to mention the new `hasPaywall` property * lint * tests: add unit tests that check the `hasPaywall` property of `Offering` * api tests: add `paywallComponents` and `hasPaywall` to SwiftAPITester
536 lines
24 KiB
Swift
536 lines
24 KiB
Swift
//
|
|
// Copyright RevenueCat Inc. All Rights Reserved.
|
|
//
|
|
// Licensed under the MIT License (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://opensource.org/licenses/MIT
|
|
//
|
|
// View+PresentPaywall.swift
|
|
//
|
|
// Created by Nacho Soto on 7/24/23.
|
|
|
|
import RevenueCat
|
|
import SwiftUI
|
|
|
|
#if !os(macOS) && !os(tvOS)
|
|
|
|
/// Presentation options to use with the [presentPaywallIfNeeded](x-source-tag://presentPaywallIfNeeded) View modifiers.
|
|
///
|
|
/// ### Related Articles
|
|
/// [Documentation](https://rev.cat/paywalls)
|
|
public enum PaywallPresentationMode {
|
|
|
|
/// Paywall presented using SwiftUI's `.sheet`.
|
|
case sheet
|
|
|
|
/// Paywall presented using SwiftUI's `.fullScreenCover`.
|
|
case fullScreen
|
|
|
|
}
|
|
|
|
extension PaywallPresentationMode {
|
|
|
|
// swiftlint:disable:next missing_docs
|
|
public static let `default`: Self = .sheet
|
|
|
|
}
|
|
|
|
/// Contains the `PerformPurchase` and `PerformRestore` blocks that are executed when
|
|
/// ``Purchases/purchasesAreCompletedBy`` is ``PurchasesAreCompletedBy/myApp``.
|
|
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
|
|
public struct MyAppPurchaseLogic {
|
|
|
|
/// When ``Purchases/purchasesAreCompletedBy`` is ``PurchasesAreCompletedBy/myApp``, this is the app-defined
|
|
/// callback method that performs the purchase.
|
|
public let performPurchase: PerformPurchase
|
|
|
|
/// When ``Purchases/purchasesAreCompletedBy`` is ``PurchasesAreCompletedBy/myApp``, this is the app-defined
|
|
/// callback method that performs the restore.
|
|
public let performRestore: PerformRestore
|
|
|
|
/// Initializes the struct with blocks that are executed when
|
|
/// ``Purchases/purchasesAreCompletedBy`` is ``PurchasesAreCompletedBy/myApp``.
|
|
public init(performPurchase: @escaping PerformPurchase, performRestore: @escaping PerformRestore) {
|
|
self.performPurchase = performPurchase
|
|
self.performRestore = performRestore
|
|
}
|
|
}
|
|
|
|
// 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")
|
|
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
|
|
/// 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 paywall 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
|
|
@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: @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 paywall 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,
|
|
myAppPurchaseLogic: MyAppPurchaseLogic? = nil,
|
|
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,
|
|
myAppPurchaseLogic: myAppPurchaseLogic,
|
|
shouldDisplay: { info in
|
|
!info.entitlements
|
|
.activeInCurrentEnvironment
|
|
.keys
|
|
.contains(requiredEntitlementIdentifier)
|
|
},
|
|
purchaseStarted: purchaseStarted,
|
|
purchaseCompleted: purchaseCompleted,
|
|
purchaseCancelled: purchaseCancelled,
|
|
restoreStarted: restoreStarted,
|
|
restoreCompleted: restoreCompleted,
|
|
purchaseFailure: purchaseFailure,
|
|
restoreFailure: restoreFailure,
|
|
onDismiss: onDismiss
|
|
)
|
|
}
|
|
|
|
// swiftlint:disable line_length
|
|
/// Presents a ``PaywallView`` based a given condition.
|
|
/// Example:
|
|
/// ```swift
|
|
/// var body: some View {
|
|
/// YourApp()
|
|
/// .presentPaywallIfNeeded {
|
|
/// !$0.entitlements.active.keys.contains("entitlement_identifier")
|
|
/// } purchaseStarted: {
|
|
/// print("Purchase started")
|
|
/// } purchaseCompleted: { customerInfo in
|
|
/// print("Customer info unlocked entitlement: \(customerInfo.entitlements)")
|
|
/// } purchaseCancelled: {
|
|
/// print("Purchase was cancelled")
|
|
/// } 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 paywall 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)
|
|
@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,
|
|
myAppPurchaseLogic: MyAppPurchaseLogic? = nil,
|
|
shouldDisplay: @escaping @Sendable (CustomerInfo) -> Bool,
|
|
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(
|
|
offering: offering,
|
|
fonts: fonts,
|
|
presentationMode: presentationMode,
|
|
myAppPurchaseLogic: myAppPurchaseLogic,
|
|
shouldDisplay: shouldDisplay,
|
|
purchaseStarted: { _ in
|
|
purchaseStarted()
|
|
},
|
|
purchaseCompleted: purchaseCompleted,
|
|
purchaseCancelled: purchaseCancelled,
|
|
restoreStarted: nil,
|
|
restoreCompleted: restoreCompleted,
|
|
purchaseFailure: purchaseFailure,
|
|
restoreFailure: restoreFailure,
|
|
onDismiss: onDismiss,
|
|
customerInfoFetcher: {
|
|
guard Purchases.isConfigured else {
|
|
throw PaywallError.purchasesNotConfigured
|
|
}
|
|
|
|
return try await Purchases.shared.customerInfo()
|
|
}
|
|
)
|
|
}
|
|
|
|
/// 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 paywall 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,
|
|
myAppPurchaseLogic: MyAppPurchaseLogic? = nil,
|
|
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,
|
|
myAppPurchaseLogic: myAppPurchaseLogic,
|
|
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,
|
|
fonts: PaywallFontProvider = DefaultPaywallFontProvider(),
|
|
introEligibility: TrialOrIntroEligibilityChecker? = nil,
|
|
purchaseHandler: PurchaseHandler? = nil,
|
|
presentationMode: PaywallPresentationMode = .default,
|
|
myAppPurchaseLogic: MyAppPurchaseLogic? = nil,
|
|
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,
|
|
customerInfoFetcher: @escaping CustomerInfoFetcher
|
|
) -> some View {
|
|
return self
|
|
.modifier(PresentingPaywallModifier(
|
|
shouldDisplay: shouldDisplay,
|
|
myAppPurchaseLogic: myAppPurchaseLogic,
|
|
presentationMode: presentationMode,
|
|
purchaseStarted: purchaseStarted,
|
|
purchaseCompleted: purchaseCompleted,
|
|
purchaseCancelled: purchaseCancelled,
|
|
restoreCompleted: restoreCompleted,
|
|
purchaseFailure: purchaseFailure,
|
|
restoreStarted: restoreStarted,
|
|
restoreFailure: restoreFailure,
|
|
onDismiss: onDismiss,
|
|
content: .optionalOffering(offering),
|
|
fontProvider: fonts,
|
|
customerInfoFetcher: customerInfoFetcher,
|
|
introEligibility: introEligibility,
|
|
purchaseHandler: purchaseHandler
|
|
))
|
|
}
|
|
|
|
}
|
|
|
|
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
|
|
@available(macOS, unavailable)
|
|
@available(tvOS, unavailable)
|
|
private struct PresentingPaywallModifier: ViewModifier {
|
|
|
|
private struct Data: Identifiable {
|
|
var customerInfo: CustomerInfo
|
|
var id: String { self.customerInfo.originalAppUserId }
|
|
}
|
|
|
|
var shouldDisplay: @Sendable (CustomerInfo) -> Bool
|
|
var presentationMode: PaywallPresentationMode
|
|
var purchaseStarted: PurchaseOfPackageStartedHandler?
|
|
var purchaseCompleted: PurchaseOrRestoreCompletedHandler?
|
|
var purchaseCancelled: PurchaseCancelledHandler?
|
|
var restoreCompleted: PurchaseOrRestoreCompletedHandler?
|
|
var purchaseFailure: PurchaseFailureHandler?
|
|
var restoreStarted: RestoreStartedHandler?
|
|
var restoreFailure: PurchaseFailureHandler?
|
|
var onDismiss: (() -> Void)?
|
|
|
|
var content: PaywallViewConfiguration.Content
|
|
var fontProvider: PaywallFontProvider
|
|
|
|
var customerInfoFetcher: View.CustomerInfoFetcher
|
|
var introEligibility: TrialOrIntroEligibilityChecker?
|
|
|
|
init(
|
|
shouldDisplay: @escaping @Sendable (CustomerInfo) -> Bool,
|
|
myAppPurchaseLogic: MyAppPurchaseLogic?,
|
|
presentationMode: PaywallPresentationMode,
|
|
purchaseStarted: PurchaseOfPackageStartedHandler?,
|
|
purchaseCompleted: PurchaseOrRestoreCompletedHandler?,
|
|
purchaseCancelled: PurchaseCancelledHandler?,
|
|
restoreCompleted: PurchaseOrRestoreCompletedHandler?,
|
|
purchaseFailure: PurchaseFailureHandler?,
|
|
restoreStarted: RestoreStartedHandler?,
|
|
restoreFailure: PurchaseFailureHandler?,
|
|
onDismiss: (() -> Void)?,
|
|
content: PaywallViewConfiguration.Content,
|
|
fontProvider: PaywallFontProvider,
|
|
customerInfoFetcher: @escaping View.CustomerInfoFetcher,
|
|
introEligibility: TrialOrIntroEligibilityChecker?,
|
|
purchaseHandler: PurchaseHandler?
|
|
) {
|
|
self.shouldDisplay = shouldDisplay
|
|
self.presentationMode = presentationMode
|
|
self.purchaseStarted = purchaseStarted
|
|
self.purchaseCompleted = purchaseCompleted
|
|
self.purchaseCancelled = purchaseCancelled
|
|
self.restoreStarted = restoreStarted
|
|
self.restoreCompleted = restoreCompleted
|
|
self.purchaseFailure = purchaseFailure
|
|
self.restoreFailure = restoreFailure
|
|
self.onDismiss = onDismiss
|
|
self.content = content
|
|
self.fontProvider = fontProvider
|
|
self.customerInfoFetcher = customerInfoFetcher
|
|
self.introEligibility = introEligibility
|
|
self._purchaseHandler = .init(wrappedValue: purchaseHandler ??
|
|
PurchaseHandler.default(performPurchase: myAppPurchaseLogic?.performPurchase,
|
|
performRestore: myAppPurchaseLogic?.performRestore))
|
|
}
|
|
|
|
@StateObject
|
|
private var purchaseHandler: PurchaseHandler
|
|
|
|
@State
|
|
private var data: Data?
|
|
|
|
func body(content: Content) -> some View {
|
|
Group {
|
|
switch presentationMode {
|
|
case .sheet:
|
|
content
|
|
.sheet(item: self.$data, onDismiss: self.onDismiss) { data in
|
|
self.paywallView(data)
|
|
}
|
|
case .fullScreen:
|
|
content
|
|
.fullScreenCover(item: self.$data, onDismiss: self.onDismiss) { data in
|
|
self.paywallView(data)
|
|
}
|
|
}
|
|
}
|
|
.task {
|
|
guard let info = try? await self.customerInfoFetcher() else { return }
|
|
|
|
Logger.debug(Strings.determining_whether_to_display_paywall)
|
|
|
|
if self.shouldDisplay(info) {
|
|
Logger.debug(Strings.displaying_paywall)
|
|
|
|
self.data = .init(customerInfo: info)
|
|
} else {
|
|
Logger.debug(Strings.not_displaying_paywall)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func paywallView(_ data: Data) -> some View {
|
|
PaywallView(
|
|
configuration: .init(
|
|
content: self.content,
|
|
customerInfo: data.customerInfo,
|
|
fonts: self.fontProvider,
|
|
displayCloseButton: true,
|
|
introEligibility: self.introEligibility,
|
|
purchaseHandler: self.purchaseHandler
|
|
)
|
|
)
|
|
.onPurchaseStarted {
|
|
self.purchaseStarted?($0)
|
|
}
|
|
.onPurchaseCompleted {
|
|
self.purchaseCompleted?($0)
|
|
}
|
|
.onPurchaseCancelled {
|
|
self.purchaseCancelled?()
|
|
}
|
|
.onRestoreStarted {
|
|
self.restoreStarted?()
|
|
}
|
|
.onRestoreCompleted { customerInfo in
|
|
self.restoreCompleted?(customerInfo)
|
|
|
|
if !self.shouldDisplay(customerInfo) {
|
|
self.close()
|
|
}
|
|
}
|
|
.onPurchaseFailure {
|
|
self.purchaseFailure?($0)
|
|
}
|
|
.onRestoreFailure {
|
|
self.restoreFailure?($0)
|
|
}
|
|
.interactiveDismissDisabled(self.purchaseHandler.actionInProgress)
|
|
}
|
|
|
|
private func close() {
|
|
Logger.debug(Strings.dismissing_paywall)
|
|
|
|
self.data = nil
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|