From 3b8a72d5d0dca827cb74e8d6acb35157bc95d3de Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Mon, 31 Jul 2023 12:40:58 +0200 Subject: [PATCH] `Paywalls` added individual previews for templates (#2924) This makes it easier to preview and iterate on each individual template instead of doing that through `PaywallView`. I extracted a lot of the common code into a new `PreviewHelpers` for this. --- .../Helpers/PaywallData+Default.swift | 29 +++++++ RevenueCatUI/Helpers/PreviewHelpers.swift | 84 +++++++++++++++++++ RevenueCatUI/PaywallView.swift | 11 +-- .../Templates/MultiPackageBoldTemplate.swift | 20 +++++ .../OnePackageStandardTemplate.swift | 20 +++++ .../OnePackageWithFeaturesTemplate.swift | 20 +++++ RevenueCatUI/Templates/TemplateViewType.swift | 2 +- RevenueCatUI/Views/FooterView.swift | 6 +- RevenueCatUI/Views/PurchaseButton.swift | 8 +- 9 files changed, 179 insertions(+), 21 deletions(-) create mode 100644 RevenueCatUI/Helpers/PreviewHelpers.swift diff --git a/RevenueCatUI/Helpers/PaywallData+Default.swift b/RevenueCatUI/Helpers/PaywallData+Default.swift index ff4753eebd..16330f2932 100644 --- a/RevenueCatUI/Helpers/PaywallData+Default.swift +++ b/RevenueCatUI/Helpers/PaywallData+Default.swift @@ -7,6 +7,7 @@ import Foundation import RevenueCat +import SwiftUI #if canImport(SwiftUI) && swift(>=5.7) @@ -65,4 +66,32 @@ private extension PaywallData { } +// MARK: - + +#if DEBUG + +@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) +@available(watchOS, unavailable) +@available(macOS, unavailable) +@available(macCatalyst, unavailable) +struct DefaultPaywall_Previews: PreviewProvider { + + static var previews: some View { + PreviewableTemplate(offering: Self.offering) { + MultiPackageBoldTemplate($0) + } + } + + static let offering = Offering( + identifier: "offering", + serverDescription: "Main offering", + metadata: [:], + paywall: .default, + availablePackages: TestData.packages + ) + +} + +#endif + #endif diff --git a/RevenueCatUI/Helpers/PreviewHelpers.swift b/RevenueCatUI/Helpers/PreviewHelpers.swift new file mode 100644 index 0000000000..ddfc70da0e --- /dev/null +++ b/RevenueCatUI/Helpers/PreviewHelpers.swift @@ -0,0 +1,84 @@ +// +// PreviewHelpers.swift +// +// +// Created by Nacho Soto on 7/29/23. +// + +import RevenueCat +import SwiftUI + +#if DEBUG + +@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) +@available(watchOS, unavailable) +@available(macOS, unavailable) +@available(macCatalyst, unavailable) +@MainActor +enum PreviewHelpers { + + static let introEligibilityChecker: TrialOrIntroEligibilityChecker = + .producing(eligibility: .eligible) + .with(delay: .seconds(0.5)) + static let purchaseHandler: PurchaseHandler = + .mock() + .with(delay: .seconds(0.5)) + +} + +/// Creates an easily previewable `TemplateViewType`. +/// Usage: +/// ```swift +/// PreviewableTemplate( +/// offering: TestData.testOffering +/// ) { +/// PaywallTemplate($0) +/// } +/// ``` +@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) +@available(macOS, unavailable) +struct PreviewableTemplate: View { + + typealias Creator = @Sendable @MainActor (TemplateViewConfiguration) -> T + + private let creator: Creator + private let configuration: Result + + @StateObject + private var introEligibilityViewModel = IntroEligibilityViewModel( + introEligibilityChecker: PreviewHelpers.introEligibilityChecker + ) + + init( + offering: Offering, + creator: @escaping Creator + ) { + self.configuration = offering.paywall!.configuration( + for: offering, + mode: .fullScreen, + locale: .current + ) + self.creator = creator + } + + var body: some View { + switch self.configuration { + case let .success(configuration): + self.creator(configuration) + .environmentObject(self.introEligibilityViewModel) + .environmentObject(PreviewHelpers.purchaseHandler) + .task { + await self.introEligibilityViewModel.computeEligibility( + for: configuration.packages + ) + } + + case let .failure(error): + DebugErrorView("Invalid configuration: \(error)", + releaseBehavior: .fatalError) + } + } + +} + +#endif diff --git a/RevenueCatUI/PaywallView.swift b/RevenueCatUI/PaywallView.swift index b3bb877c82..c0dbb3e04d 100644 --- a/RevenueCatUI/PaywallView.swift +++ b/RevenueCatUI/PaywallView.swift @@ -226,8 +226,8 @@ struct PaywallView_Previews: PreviewProvider { PaywallView( offering: offering, mode: mode, - introEligibility: Self.introEligibility, - purchaseHandler: Self.purchaseHandler + introEligibility: PreviewHelpers.introEligibilityChecker, + purchaseHandler: PreviewHelpers.purchaseHandler ) .previewLayout(mode.layout) .previewDisplayName("\(offering.paywall?.template.name ?? "")-\(mode)") @@ -235,13 +235,6 @@ struct PaywallView_Previews: PreviewProvider { } } - private static let introEligibility: TrialOrIntroEligibilityChecker = - .producing(eligibility: .eligible) - .with(delay: .seconds(0.5)) - private static let purchaseHandler: PurchaseHandler = - .mock() - .with(delay: .seconds(1)) - private static let offerings: [Offering] = [ TestData.offeringWithIntroOffer, TestData.offeringWithMultiPackagePaywall, diff --git a/RevenueCatUI/Templates/MultiPackageBoldTemplate.swift b/RevenueCatUI/Templates/MultiPackageBoldTemplate.swift index 78fcd00e89..e473544095 100644 --- a/RevenueCatUI/Templates/MultiPackageBoldTemplate.swift +++ b/RevenueCatUI/Templates/MultiPackageBoldTemplate.swift @@ -226,3 +226,23 @@ private extension MultiPackageBoldTemplate { } } + +// MARK: - + +#if DEBUG + +@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) +@available(watchOS, unavailable) +@available(macOS, unavailable) +@available(macCatalyst, unavailable) +struct MultiPackageBoldTemplate_Previews: PreviewProvider { + + static var previews: some View { + PreviewableTemplate(offering: TestData.offeringWithMultiPackagePaywall) { + MultiPackageBoldTemplate($0) + } + } + +} + +#endif diff --git a/RevenueCatUI/Templates/OnePackageStandardTemplate.swift b/RevenueCatUI/Templates/OnePackageStandardTemplate.swift index db6269de0b..41a98b6913 100644 --- a/RevenueCatUI/Templates/OnePackageStandardTemplate.swift +++ b/RevenueCatUI/Templates/OnePackageStandardTemplate.swift @@ -170,3 +170,23 @@ private extension PaywallViewMode { } } + +// MARK: - + +#if DEBUG + +@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) +@available(watchOS, unavailable) +@available(macOS, unavailable) +@available(macCatalyst, unavailable) +struct OnePackageStandardTemplate_Previews: PreviewProvider { + + static var previews: some View { + PreviewableTemplate(offering: TestData.offeringWithIntroOffer) { + OnePackageStandardTemplate($0) + } + } + +} + +#endif diff --git a/RevenueCatUI/Templates/OnePackageWithFeaturesTemplate.swift b/RevenueCatUI/Templates/OnePackageWithFeaturesTemplate.swift index 39b31e9a03..c8ac24fc9f 100644 --- a/RevenueCatUI/Templates/OnePackageWithFeaturesTemplate.swift +++ b/RevenueCatUI/Templates/OnePackageWithFeaturesTemplate.swift @@ -175,3 +175,23 @@ private struct FeatureView: View { private static let cutoffForHorizontalLayout: DynamicTypeSize = .xxxLarge } + +// MARK: - + +#if DEBUG + +@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) +@available(watchOS, unavailable) +@available(macOS, unavailable) +@available(macCatalyst, unavailable) +struct OnePackageWithFeaturesTemplate_Previews: PreviewProvider { + + static var previews: some View { + PreviewableTemplate(offering: TestData.offeringWithSinglePackageFeaturesPaywall) { + OnePackageWithFeaturesTemplate($0) + } + } + +} + +#endif diff --git a/RevenueCatUI/Templates/TemplateViewType.swift b/RevenueCatUI/Templates/TemplateViewType.swift index 9d1a54e949..dce90e7996 100644 --- a/RevenueCatUI/Templates/TemplateViewType.swift +++ b/RevenueCatUI/Templates/TemplateViewType.swift @@ -51,7 +51,7 @@ extension PaywallData { } } - private func configuration( + func configuration( for offering: Offering, mode: PaywallViewMode, locale: Locale diff --git a/RevenueCatUI/Views/FooterView.swift b/RevenueCatUI/Views/FooterView.swift index ad0b14c93c..bd3e6d23b3 100644 --- a/RevenueCatUI/Views/FooterView.swift +++ b/RevenueCatUI/Views/FooterView.swift @@ -171,14 +171,10 @@ struct Footer_Previews: PreviewProvider { privacyURL: privacyURL ), color: TestData.colors.text1Color, - purchaseHandler: Self.handler + purchaseHandler: PreviewHelpers.purchaseHandler ) } - private static let handler: PurchaseHandler = - .mock() - .with(delay: .seconds(0.5)) - } #endif diff --git a/RevenueCatUI/Views/PurchaseButton.swift b/RevenueCatUI/Views/PurchaseButton.swift index fe94783e1c..32b91bfe97 100644 --- a/RevenueCatUI/Views/PurchaseButton.swift +++ b/RevenueCatUI/Views/PurchaseButton.swift @@ -119,18 +119,14 @@ struct PurchaseButton_Previews: PreviewProvider { localization: TestData.localization1.processVariables(with: Self.package, locale: .current), introEligibility: self.eligibility, mode: self.mode, - purchaseHandler: Self.purchaseHandler + purchaseHandler: PreviewHelpers.purchaseHandler ) .task { - self.eligibility = await Self.eligibilityChecker.eligibility(for: Self.package) + self.eligibility = await PreviewHelpers.introEligibilityChecker.eligibility(for: Self.package) } } private static let package: Package = TestData.packageWithIntroOffer - private static let eligibilityChecker: TrialOrIntroEligibilityChecker = .producing(eligibility: .eligible) - .with(delay: .seconds(0.3)) - private static let purchaseHandler: PurchaseHandler = .mock() - .with(delay: .seconds(0.5)) }