SelfieCam/SelfieCam/Shared/Premium/PaywallPresenter.swift

127 lines
3.7 KiB
Swift

//
// PaywallPresenter.swift
// SelfieCam
//
// Presents RevenueCat native Paywall with automatic fallback to custom PaywallView
// if the RevenueCat paywall fails to load or is not configured.
//
import SwiftUI
import RevenueCat
import RevenueCatUI
import Bedrock
/// Presents RevenueCat Paywall with fallback to custom paywall.
///
/// Usage:
/// ```swift
/// .sheet(isPresented: $showPaywall) {
/// PaywallPresenter()
/// }
/// ```
///
/// The presenter will:
/// 1. Attempt to load the RevenueCat paywall from your configured offering
/// 2. If successful, display the native RevenueCat PaywallView
/// 3. If it fails (network error, no paywall configured), fall back to ProPaywallView
struct PaywallPresenter: View {
@Environment(\.dismiss) private var dismiss
@State private var offering: Offering?
@State private var isLoading = true
@State private var useFallback = false
var body: some View {
Group {
if isLoading {
// Loading state while fetching offerings
loadingView
} else if useFallback {
// Fallback to custom paywall on error
ProPaywallView()
} else if let offering {
// RevenueCat native paywall
paywallView(for: offering)
} else {
// No offering available, use fallback
ProPaywallView()
}
}
.task {
await loadOffering()
}
}
// MARK: - Loading View
private var loadingView: some View {
VStack {
ProgressView()
.scaleEffect(1.5)
.tint(.white)
Text(String(localized: "Loading..."))
.font(.system(size: Design.FontSize.body))
.foregroundStyle(.white.opacity(Design.Opacity.medium))
.padding(.top, Design.Spacing.medium)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(AppSurface.primary)
}
// MARK: - Paywall View
private func paywallView(for offering: Offering) -> some View {
PaywallView(offering: offering)
.onPurchaseCompleted { customerInfo in
#if DEBUG
print("✅ [PaywallPresenter] Purchase completed")
#endif
dismiss()
}
.onRestoreCompleted { customerInfo in
#if DEBUG
print("✅ [PaywallPresenter] Restore completed")
#endif
dismiss()
}
}
// MARK: - Load Offering
private func loadOffering() async {
do {
let offerings = try await Purchases.shared.offerings()
// Check if current offering has a paywall configured
if let current = offerings.current {
offering = current
isLoading = false
#if DEBUG
print("✅ [PaywallPresenter] Loaded offering: \(current.identifier)")
#endif
} else {
// No current offering, use fallback
#if DEBUG
print("⚠️ [PaywallPresenter] No current offering available, using fallback")
#endif
useFallback = true
isLoading = false
}
} catch {
#if DEBUG
print("⚠️ [PaywallPresenter] Failed to load offerings: \(error)")
#endif
useFallback = true
isLoading = false
}
}
}
// MARK: - Preview
#Preview {
PaywallPresenter()
.preferredColorScheme(.dark)
}