Files
purchases-ios/RevenueCatUI/Views/LoadingPaywallView.swift
T
Josh Holtz 8f71dd9199 [Paywalls V2] Process variables in the text component (#4490)
* Start of processing variables in text

* Moved some files

* Fixed lint and added logic for zero decimal place

* Fix test

* Apply new PackageContext in package and better preview

* Fixed lint
2024-11-19 05:55:37 -06:00

257 lines
7.6 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
//
// LoadingPaywallView.swift
//
// Created by Nacho Soto on 7/21/23.
import RevenueCat
import SwiftUI
#if !os(macOS) && !os(tvOS)
/// A `PaywallView` suitable to be displayed as a loading placeholder.
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
@available(macOS, unavailable)
@available(tvOS, unavailable)
@MainActor
struct LoadingPaywallView: View {
var mode: PaywallViewMode
var displayCloseButton: Bool
var shimmer: Bool = true
var body: some View {
LoadedOfferingPaywallView(
offering: .init(
identifier: Self.offeringIdentifier,
serverDescription: "",
metadata: [:],
paywall: Self.paywall,
availablePackages: Self.packages
),
activelySubscribedProductIdentifiers: [],
paywall: Self.defaultPaywall,
template: Self.template,
mode: self.mode,
fonts: DefaultPaywallFontProvider(),
displayCloseButton: self.displayCloseButton,
introEligibility: Self.introEligibility,
purchaseHandler: Self.purchaseHandler,
locale: Locale.current,
showZeroDecimalPlacePrices: true
)
.allowsHitTesting(false)
.redacted(reason: .placeholder)
.shimmering(enable: self.mode.shouldDisplayBackground && self.shimmer)
.background {
TemplateBackgroundImageView(
url: Self.defaultPaywall.backgroundImageURL,
lowResUrl: Self.defaultPaywall.backgroundLowResImageURL,
blurred: true,
ignoreSafeArea: self.mode.shouldDisplayBackground
)
}
}
private static let template: PaywallTemplate = PaywallData.defaultTemplate
private static let defaultPaywall: PaywallData = .createDefault(with: Self.packages, locale: .current)
private static let paywall: PaywallData = {
var paywall: PaywallData = Self.defaultPaywall
// Hide background so it doesn't get shimmer
paywall.config.images.background = nil
return paywall
}()
private static let packages: [Package] = [
Self.monthlyPackage,
Self.weeklyPackage,
Self.annualPackage
]
}
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
@available(macOS, unavailable)
@available(tvOS, unavailable)
private extension LoadingPaywallView {
static let introEligibility: TrialOrIntroEligibilityChecker = .init(checker: { packages in
return Dictionary(
uniqueKeysWithValues: packages.map { ($0, .unknown) }
)
})
static let purchaseHandler: PurchaseHandler = .init(purchases: LoadingPaywallPurchases())
static let offeringIdentifier = "offering"
static let weeklyPackage = Package(
identifier: "weekly",
packageType: .weekly,
storeProduct: Self.weeklyProduct.toStoreProduct(),
offeringIdentifier: Self.offeringIdentifier
)
static let monthlyPackage = Package(
identifier: "monthly",
packageType: .monthly,
storeProduct: Self.monthlyProduct.toStoreProduct(),
offeringIdentifier: Self.offeringIdentifier
)
static let annualPackage = Package(
identifier: "annual",
packageType: .annual,
storeProduct: Self.annualProduct.toStoreProduct(),
offeringIdentifier: Self.offeringIdentifier
)
static let weeklyProduct = TestStoreProduct(
localizedTitle: "Weekly",
price: 1.99,
localizedPriceString: "$1.99",
productIdentifier: "com.revenuecat.product_1",
productType: .autoRenewableSubscription,
localizedDescription: "PRO weekly",
subscriptionGroupIdentifier: "group",
subscriptionPeriod: .init(value: 1, unit: .week)
)
static let monthlyProduct = TestStoreProduct(
localizedTitle: "Monthly",
price: 12.99,
localizedPriceString: "$12.99",
productIdentifier: "com.revenuecat.product_2",
productType: .autoRenewableSubscription,
localizedDescription: "PRO monthly",
subscriptionGroupIdentifier: "group",
subscriptionPeriod: .init(value: 1, unit: .month)
)
static let annualProduct = TestStoreProduct(
localizedTitle: "Annual",
price: 69.49,
localizedPriceString: "$69.49",
productIdentifier: "com.revenuecat.product_3",
productType: .autoRenewableSubscription,
localizedDescription: "PRO annual",
subscriptionGroupIdentifier: "group",
subscriptionPeriod: .init(value: 1, unit: .year)
)
}
@available(iOS 15.0, macOS 12.0, tvOS 15.0, *)
private final class LoadingPaywallPurchases: PaywallPurchasesType {
var purchasesAreCompletedBy: PurchasesAreCompletedBy {
get { return .myApp }
set { _ = newValue }
}
func customerInfo() async throws -> RevenueCat.CustomerInfo {
fatalError("Should not be able to purchase")
}
func purchase(package: Package) async throws -> PurchaseResultData {
fatalError("Should not be able to purchase")
}
func restorePurchases() async throws -> CustomerInfo {
fatalError("Should not be able to purchase")
}
func track(paywallEvent: PaywallEvent) async {
// Ignoring events from loading paywall view
}
}
// MARK: - Shimmer
@available(iOS 15.0, macOS 12.0, tvOS 15.0, *)
private struct Shimmer: ViewModifier {
var duration: CGFloat
var autoreverse: Bool
@State
private var initialState: Bool = true
func body(content: Content) -> some View {
content
.mask(
LinearGradient(
gradient: .init(colors: [
.black.opacity(0.3),
.black,
.black.opacity(0.3)
]),
startPoint: self.startPoint,
endPoint: self.endPoint
)
.edgesIgnoringSafeArea(.all)
)
.onAppear {
withAnimation(
.linear(duration: self.duration)
.delay(self.duration / 2.0)
.repeatForever(autoreverses: self.autoreverse)
) {
self.initialState.toggle()
}
}
}
private var startPoint: UnitPoint {
return self.initialState ? UnitPoint(x: -0.3, y: -0.3) : UnitPoint(x: 1, y: 1)
}
private var endPoint: UnitPoint {
return self.initialState ? UnitPoint(x: 0, y: 0) : UnitPoint(x: 1.3, y: 1.3)
}
}
@available(iOS 15.0, macOS 12.0, tvOS 15.0, *)
private extension View {
@ViewBuilder
func shimmering(
enable: Bool,
duration: Double = 1.5,
autoreverse: Bool = false
) -> some View {
if enable {
self.modifier(Shimmer(duration: duration, autoreverse: autoreverse))
} else {
self
}
}
}
// MARK: -
#if DEBUG
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
@available(macOS, unavailable)
@available(tvOS, unavailable)
struct LoadingPaywallView_Previews: PreviewProvider {
static var previews: some View {
ForEach(PaywallViewMode.allCases, id: \.self) { mode in
LoadingPaywallView(mode: mode, displayCloseButton: true)
.previewDisplayName("\(mode)")
}
}
}
#endif
#endif