2023-07-24 15:49:26 +02:00
|
|
|
//
|
2023-08-24 14:18:04 -07:00
|
|
|
// 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
|
|
|
|
|
//
|
2023-07-24 15:49:26 +02:00
|
|
|
// LoadingPaywallView.swift
|
|
|
|
|
//
|
|
|
|
|
// Created by Nacho Soto on 7/21/23.
|
|
|
|
|
|
|
|
|
|
import RevenueCat
|
|
|
|
|
import SwiftUI
|
|
|
|
|
|
2023-11-16 12:49:53 -08:00
|
|
|
#if !os(macOS) && !os(tvOS)
|
2023-11-08 07:35:19 -08:00
|
|
|
|
2023-07-24 15:49:26 +02:00
|
|
|
/// A `PaywallView` suitable to be displayed as a loading placeholder.
|
2023-11-08 07:35:19 -08:00
|
|
|
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
|
2023-08-03 12:11:44 -07:00
|
|
|
@available(macOS, unavailable)
|
|
|
|
|
@available(tvOS, unavailable)
|
2023-07-31 12:02:30 +02:00
|
|
|
@MainActor
|
2023-07-24 15:49:26 +02:00
|
|
|
struct LoadingPaywallView: View {
|
|
|
|
|
|
2023-08-09 20:51:03 -07:00
|
|
|
var mode: PaywallViewMode
|
2023-12-14 09:29:44 -08:00
|
|
|
var displayCloseButton: Bool
|
2023-08-09 20:51:03 -07:00
|
|
|
|
2023-10-03 22:09:06 -05:00
|
|
|
var shimmer: Bool = true
|
|
|
|
|
|
2023-07-24 15:49:26 +02:00
|
|
|
var body: some View {
|
|
|
|
|
LoadedOfferingPaywallView(
|
|
|
|
|
offering: .init(
|
|
|
|
|
identifier: Self.offeringIdentifier,
|
|
|
|
|
serverDescription: "",
|
|
|
|
|
metadata: [:],
|
2023-10-03 22:09:06 -05:00
|
|
|
paywall: Self.paywall,
|
2023-08-03 13:26:49 -07:00
|
|
|
availablePackages: Self.packages
|
2023-07-24 15:49:26 +02:00
|
|
|
),
|
2023-08-24 09:54:44 -07:00
|
|
|
activelySubscribedProductIdentifiers: [],
|
2023-08-03 13:26:49 -07:00
|
|
|
paywall: Self.defaultPaywall,
|
2023-08-16 15:51:07 -07:00
|
|
|
template: Self.template,
|
2023-08-09 20:51:03 -07:00
|
|
|
mode: self.mode,
|
2023-08-08 15:34:10 -07:00
|
|
|
fonts: DefaultPaywallFontProvider(),
|
2023-12-14 09:29:44 -08:00
|
|
|
displayCloseButton: self.displayCloseButton,
|
2023-07-24 15:49:26 +02:00
|
|
|
introEligibility: Self.introEligibility,
|
2024-08-20 09:44:27 -05:00
|
|
|
purchaseHandler: Self.purchaseHandler,
|
2024-11-19 05:55:37 -06:00
|
|
|
locale: Locale.current,
|
|
|
|
|
showZeroDecimalPlacePrices: true
|
2023-07-24 15:49:26 +02:00
|
|
|
)
|
2023-10-03 14:37:26 -05:00
|
|
|
.allowsHitTesting(false)
|
2023-07-24 15:49:26 +02:00
|
|
|
.redacted(reason: .placeholder)
|
2023-10-24 07:56:45 -07:00
|
|
|
.shimmering(enable: self.mode.shouldDisplayBackground && self.shimmer)
|
2023-10-03 22:09:06 -05:00
|
|
|
.background {
|
|
|
|
|
TemplateBackgroundImageView(
|
|
|
|
|
url: Self.defaultPaywall.backgroundImageURL,
|
2024-05-27 21:39:18 +02:00
|
|
|
lowResUrl: Self.defaultPaywall.backgroundLowResImageURL,
|
2023-10-24 07:56:45 -07:00
|
|
|
blurred: true,
|
|
|
|
|
ignoreSafeArea: self.mode.shouldDisplayBackground
|
2023-10-03 22:09:06 -05:00
|
|
|
)
|
|
|
|
|
}
|
2023-07-24 15:49:26 +02:00
|
|
|
}
|
|
|
|
|
|
2023-08-16 15:51:07 -07:00
|
|
|
private static let template: PaywallTemplate = PaywallData.defaultTemplate
|
2023-10-10 16:25:26 -07:00
|
|
|
private static let defaultPaywall: PaywallData = .createDefault(with: Self.packages, locale: .current)
|
2023-08-03 13:26:49 -07:00
|
|
|
|
2023-10-03 22:09:06 -05:00
|
|
|
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
|
|
|
|
|
}()
|
|
|
|
|
|
2023-08-03 13:26:49 -07:00
|
|
|
private static let packages: [Package] = [
|
|
|
|
|
Self.monthlyPackage,
|
|
|
|
|
Self.weeklyPackage,
|
|
|
|
|
Self.annualPackage
|
|
|
|
|
]
|
|
|
|
|
|
2023-07-24 15:49:26 +02:00
|
|
|
}
|
|
|
|
|
|
2023-11-08 07:35:19 -08:00
|
|
|
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
|
2023-08-03 12:11:44 -07:00
|
|
|
@available(macOS, unavailable)
|
|
|
|
|
@available(tvOS, unavailable)
|
2023-07-24 15:49:26 +02:00
|
|
|
private extension LoadingPaywallView {
|
|
|
|
|
|
|
|
|
|
static let introEligibility: TrialOrIntroEligibilityChecker = .init(checker: { packages in
|
|
|
|
|
return Dictionary(
|
|
|
|
|
uniqueKeysWithValues: packages.map { ($0, .unknown) }
|
|
|
|
|
)
|
|
|
|
|
})
|
2023-09-13 17:44:27 -07:00
|
|
|
static let purchaseHandler: PurchaseHandler = .init(purchases: LoadingPaywallPurchases())
|
2023-07-24 15:49:26 +02:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
)
|
|
|
|
|
}
|
2023-07-31 20:56:08 +02:00
|
|
|
|
2023-09-13 17:44:27 -07:00
|
|
|
@available(iOS 15.0, macOS 12.0, tvOS 15.0, *)
|
|
|
|
|
private final class LoadingPaywallPurchases: PaywallPurchasesType {
|
|
|
|
|
|
2024-07-05 12:05:44 -07:00
|
|
|
var purchasesAreCompletedBy: PurchasesAreCompletedBy {
|
|
|
|
|
get { return .myApp }
|
|
|
|
|
set { _ = newValue }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func customerInfo() async throws -> RevenueCat.CustomerInfo {
|
|
|
|
|
fatalError("Should not be able to purchase")
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-13 17:44:27 -07:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-03 22:09:06 -05:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-31 20:56:08 +02:00
|
|
|
// MARK: -
|
|
|
|
|
|
|
|
|
|
#if DEBUG
|
|
|
|
|
|
2023-11-16 12:49:53 -08:00
|
|
|
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
|
2023-07-31 20:56:08 +02:00
|
|
|
@available(macOS, unavailable)
|
2023-08-03 12:11:44 -07:00
|
|
|
@available(tvOS, unavailable)
|
2023-07-31 20:56:08 +02:00
|
|
|
struct LoadingPaywallView_Previews: PreviewProvider {
|
|
|
|
|
|
|
|
|
|
static var previews: some View {
|
2023-08-09 20:51:03 -07:00
|
|
|
ForEach(PaywallViewMode.allCases, id: \.self) { mode in
|
2023-12-14 09:29:44 -08:00
|
|
|
LoadingPaywallView(mode: mode, displayCloseButton: true)
|
2023-08-09 20:51:03 -07:00
|
|
|
.previewDisplayName("\(mode)")
|
|
|
|
|
}
|
2023-07-31 20:56:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|
2023-11-08 07:35:19 -08:00
|
|
|
|
|
|
|
|
#endif
|