Skip to content

Commit

Permalink
Paywalls: handle already purchased state (#3046)
Browse files Browse the repository at this point in the history
  • Loading branch information
NachoSoto committed Sep 14, 2023
1 parent 811c91c commit 55eafd1
Show file tree
Hide file tree
Showing 22 changed files with 187 additions and 131 deletions.
6 changes: 6 additions & 0 deletions RevenueCatUI/Data/TemplateViewConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ extension TemplateViewConfiguration {

let content: RevenueCat.Package
let localization: ProcessedLocalizedConfiguration
let currentlySubscribed: Bool
let discountRelativeToMostExpensivePerMonth: Double?

}
Expand Down Expand Up @@ -98,8 +99,10 @@ extension TemplateViewConfiguration.PackageConfiguration {

/// Creates a `PackageConfiguration` based on `setting`.
/// - Throws: `TemplateError`
// swiftlint:disable:next function_parameter_count
static func create(
with packages: [RevenueCat.Package],
activelySubscribedProductIdentifiers: Set<String>,
filter: [String],
default: String?,
localization: PaywallData.LocalizedConfiguration,
Expand All @@ -117,6 +120,9 @@ extension TemplateViewConfiguration.PackageConfiguration {
TemplateViewConfiguration.Package(
content: package,
localization: localization.processVariables(with: package, locale: locale),
currentlySubscribed: activelySubscribedProductIdentifiers.contains(
package.storeProduct.productIdentifier
),
discountRelativeToMostExpensivePerMonth: Self.discount(
from: package.storeProduct.pricePerMonth?.doubleValue,
relativeTo: mostExpensivePricePerMonth
Expand Down
2 changes: 2 additions & 0 deletions RevenueCatUI/Helpers/PreviewHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ struct PreviewableTemplate<T: TemplateViewType>: View {

init(
offering: Offering,
activelySubscribedProductIdentifiers: Set<String> = [],
mode: PaywallViewMode = .default,
presentInSheet: Bool = false,
creator: @escaping Creator
Expand All @@ -61,6 +62,7 @@ struct PreviewableTemplate<T: TemplateViewType>: View {

self.configuration = paywall.configuration(
for: offering,
activelySubscribedProductIdentifiers: activelySubscribedProductIdentifiers,
template: PaywallTemplate(rawValue: paywall.templateName)!,
mode: mode,
fonts: DefaultPaywallFontProvider(),
Expand Down
27 changes: 23 additions & 4 deletions RevenueCatUI/PaywallView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public struct PaywallView: View {
@State
private var offering: Offering?
@State
private var customerInfo: CustomerInfo?
@State
private var error: NSError?

/// Create a view that loads the `Offerings.current`.
Expand All @@ -32,6 +34,7 @@ public struct PaywallView: View {
) {
self.init(
offering: nil,
customerInfo: nil,
fonts: fonts,
introEligibility: .default(),
purchaseHandler: .default()
Expand All @@ -48,6 +51,7 @@ public struct PaywallView: View {
) {
self.init(
offering: offering,
customerInfo: nil,
fonts: fonts,
introEligibility: .default(),
purchaseHandler: .default()
Expand All @@ -56,12 +60,14 @@ public struct PaywallView: View {

init(
offering: Offering?,
customerInfo: CustomerInfo?,
mode: PaywallViewMode = .default,
fonts: PaywallFontProvider = DefaultPaywallFontProvider(),
introEligibility: TrialOrIntroEligibilityChecker?,
purchaseHandler: PurchaseHandler?
) {
self._offering = .init(initialValue: offering)
self._customerInfo = .init(initialValue: customerInfo)
self.introEligibility = introEligibility
self.purchaseHandler = purchaseHandler
self.mode = mode
Expand All @@ -79,8 +85,9 @@ public struct PaywallView: View {
private var content: some View {
VStack { // Necessary to work around FB12674350 and FB12787354
if let checker = self.introEligibility, let purchaseHandler = self.purchaseHandler {
if let offering = self.offering {
if let offering = self.offering, let customerInfo = self.customerInfo {
self.paywallView(for: offering,
activelySubscribedProductIdentifiers: customerInfo.activeSubscriptions,
fonts: self.fonts,
checker: checker,
purchaseHandler: purchaseHandler)
Expand All @@ -94,11 +101,16 @@ public struct PaywallView: View {
throw PaywallError.purchasesNotConfigured
}

guard let offering = try await Purchases.shared.offerings().current else {
throw PaywallError.noCurrentOffering
if self.offering == nil {
guard let offering = try await Purchases.shared.offerings().current else {
throw PaywallError.noCurrentOffering
}
self.offering = offering
}

self.offering = offering
if self.customerInfo == nil {
self.customerInfo = try await Purchases.shared.customerInfo()
}
} catch let error as NSError {
self.error = error
}
Expand All @@ -113,6 +125,7 @@ public struct PaywallView: View {
@ViewBuilder
private func paywallView(
for offering: Offering,
activelySubscribedProductIdentifiers: Set<String>,
fonts: PaywallFontProvider,
checker: TrialOrIntroEligibilityChecker,
purchaseHandler: PurchaseHandler
Expand All @@ -121,6 +134,7 @@ public struct PaywallView: View {

let paywallView = LoadedOfferingPaywallView(
offering: offering,
activelySubscribedProductIdentifiers: activelySubscribedProductIdentifiers,
paywall: paywall,
template: template,
mode: self.mode,
Expand Down Expand Up @@ -151,6 +165,7 @@ public struct PaywallView: View {
struct LoadedOfferingPaywallView: View {

private let offering: Offering
private let activelySubscribedProductIdentifiers: Set<String>
private let paywall: PaywallData
private let template: PaywallTemplate
private let mode: PaywallViewMode
Expand All @@ -166,6 +181,7 @@ struct LoadedOfferingPaywallView: View {

init(
offering: Offering,
activelySubscribedProductIdentifiers: Set<String>,
paywall: PaywallData,
template: PaywallTemplate,
mode: PaywallViewMode,
Expand All @@ -174,6 +190,7 @@ struct LoadedOfferingPaywallView: View {
purchaseHandler: PurchaseHandler
) {
self.offering = offering
self.activelySubscribedProductIdentifiers = activelySubscribedProductIdentifiers
self.paywall = paywall
self.template = template
self.mode = mode
Expand All @@ -187,6 +204,7 @@ struct LoadedOfferingPaywallView: View {
var body: some View {
let view = self.paywall
.createView(for: self.offering,
activelySubscribedProductIdentifiers: self.activelySubscribedProductIdentifiers,
template: self.template,
mode: self.mode,
fonts: self.fonts,
Expand Down Expand Up @@ -227,6 +245,7 @@ struct PaywallView_Previews: PreviewProvider {
ForEach(Self.modes, id: \.self) { mode in
PaywallView(
offering: offering,
customerInfo: TestData.customerInfo,
mode: mode,
introEligibility: PreviewHelpers.introEligibilityChecker,
purchaseHandler: PreviewHelpers.purchaseHandler
Expand Down
5 changes: 5 additions & 0 deletions RevenueCatUI/Templates/TemplateViewType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,14 @@ extension PaywallData {
@ViewBuilder
// swiftlint:disable:next function_parameter_count
func createView(for offering: Offering,
activelySubscribedProductIdentifiers: Set<String>,
template: PaywallTemplate,
mode: PaywallViewMode,
fonts: PaywallFontProvider,
introEligibility: IntroEligibilityViewModel,
locale: Locale) -> some View {
switch self.configuration(for: offering,
activelySubscribedProductIdentifiers: activelySubscribedProductIdentifiers,
template: template,
mode: mode,
fonts: fonts,
Expand All @@ -84,8 +86,10 @@ extension PaywallData {
}
}

// swiftlint:disable:next function_parameter_count
func configuration(
for offering: Offering,
activelySubscribedProductIdentifiers: Set<String>,
template: PaywallTemplate,
mode: PaywallViewMode,
fonts: PaywallFontProvider,
Expand All @@ -95,6 +99,7 @@ extension PaywallData {
TemplateViewConfiguration(
mode: mode,
packages: try .create(with: offering.availablePackages,
activelySubscribedProductIdentifiers: activelySubscribedProductIdentifiers,
filter: self.config.packages,
default: self.config.defaultPackage,
localization: self.localizedConfiguration,
Expand Down
16 changes: 11 additions & 5 deletions RevenueCatUI/View+PresentPaywall.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ extension View {
@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 purchaseCompleted: PurchaseCompletedHandler?
var offering: Offering?
Expand All @@ -115,27 +120,28 @@ private struct PresentingPaywallModifier: ViewModifier {
var purchaseHandler: PurchaseHandler?

@State
private var isDisplayed = false
private var data: Data?

func body(content: Content) -> some View {
content
.sheet(isPresented: self.$isDisplayed) {
.sheet(item: self.$data) { data in
NavigationView {
PaywallView(
offering: self.offering,
customerInfo: data.customerInfo,
fonts: self.fontProvider,
introEligibility: self.introEligibility ?? .default(),
purchaseHandler: self.purchaseHandler ?? .default()
)
.onPurchaseCompleted {
self.purchaseCompleted?($0)

self.isDisplayed = false
self.data = nil
}
.toolbar {
ToolbarItem(placement: .destructiveAction) {
Button {
self.isDisplayed = false
self.data = nil
} label: {
Image(systemName: "xmark")
}
Expand All @@ -151,7 +157,7 @@ private struct PresentingPaywallModifier: ViewModifier {
if self.shouldDisplay(info) {
Logger.debug(Strings.displaying_paywall)

self.isDisplayed = true
self.data = .init(customerInfo: info)
} else {
Logger.debug(Strings.not_displaying_paywall)
}
Expand Down
6 changes: 6 additions & 0 deletions RevenueCatUI/View+PresentPaywallFooter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ extension View {
) -> some View {
return self.paywallFooter(
offering: nil,
customerInfo: nil,
condensed: condensed,
fonts: fonts,
introEligibility: nil,
Expand All @@ -55,6 +56,7 @@ extension View {
) -> some View {
return self.paywallFooter(
offering: offering,
customerInfo: nil,
condensed: condensed,
fonts: fonts,
introEligibility: nil,
Expand All @@ -64,6 +66,7 @@ extension View {

func paywallFooter(
offering: Offering?,
customerInfo: CustomerInfo?,
condensed: Bool = false,
fonts: PaywallFontProvider = DefaultPaywallFontProvider(),
introEligibility: TrialOrIntroEligibilityChecker? = nil,
Expand All @@ -73,6 +76,7 @@ extension View {
return self
.modifier(PresentingPaywallFooterModifier(
offering: offering,
customerInfo: customerInfo,
condensed: condensed,
purchaseCompleted: purchaseCompleted,
fontProvider: fonts,
Expand All @@ -88,6 +92,7 @@ extension View {
private struct PresentingPaywallFooterModifier: ViewModifier {

let offering: Offering?
let customerInfo: CustomerInfo?
let condensed: Bool

let purchaseCompleted: PurchaseCompletedHandler?
Expand All @@ -100,6 +105,7 @@ private struct PresentingPaywallFooterModifier: ViewModifier {
.safeAreaInset(edge: .bottom) {
PaywallView(
offering: self.offering,
customerInfo: self.customerInfo,
mode: self.condensed ? .condensedFooter : .footer,
fonts: self.fontProvider,
introEligibility: self.introEligibility ?? .default(),
Expand Down
1 change: 1 addition & 0 deletions RevenueCatUI/Views/LoadingPaywallView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ struct LoadingPaywallView: View {
paywall: Self.defaultPaywall,
availablePackages: Self.packages
),
activelySubscribedProductIdentifiers: [],
paywall: Self.defaultPaywall,
template: Self.template,
mode: self.mode,
Expand Down
2 changes: 2 additions & 0 deletions RevenueCatUI/Views/PurchaseButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ struct PurchaseButton: View {
.buttonStyle(.borderedProminent)
.frame(maxWidth: .infinity)
.dynamicTypeSize(...Constants.maximumDynamicTypeSize)
.disabled(self.package.currentlySubscribed)
}

}
Expand Down Expand Up @@ -156,6 +157,7 @@ struct PurchaseButton_Previews: PreviewProvider {
content: TestData.packageWithIntroOffer,
localization: TestData.localization1.processVariables(with: TestData.packageWithIntroOffer,
locale: .current),
currentlySubscribed: Bool.random(),
discountRelativeToMostExpensivePerMonth: nil
)
}
Expand Down
16 changes: 16 additions & 0 deletions Tests/RevenueCatUITests/BaseSnapshotTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Created by Nacho Soto on 7/17/23.
//
import Nimble
import RevenueCat
@testable import RevenueCatUI
import SnapshotTesting
import SwiftUI
Expand All @@ -20,6 +21,21 @@ class BaseSnapshotTest: TestCase {
// isRecording = true
}

static func createPaywall(
offering: Offering,
mode: PaywallViewMode = .default,
fonts: PaywallFontProvider = DefaultPaywallFontProvider(),
introEligibility: TrialOrIntroEligibilityChecker = BaseSnapshotTest.eligibleChecker,
purchaseHandler: PurchaseHandler = BaseSnapshotTest.purchaseHandler
) -> some View {
return PaywallView(offering: offering,
customerInfo: TestData.customerInfo,
mode: mode,
fonts: fonts,
introEligibility: eligibleChecker,
purchaseHandler: purchaseHandler)
}

}

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
Expand Down
Loading

0 comments on commit 55eafd1

Please sign in to comment.