Implement freemium model with StoreKit 2: - StoreManager singleton for purchase/restore/entitlements - ProFeature enum defining gated features - PaywallView and OnboardingPaywallView for upsell UI - ProGate view modifier and ProBadge component Feature gating: - Trip saving: 1 free trip, then requires Pro - PDF export: Pro only with badge indicator - Progress tab: Shows ProLockedView for free users - Settings: Subscription management section Also fixes pre-existing test issues with StadiumVisit and ItineraryOption model signature changes. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
72 lines
1.9 KiB
Swift
72 lines
1.9 KiB
Swift
//
|
|
// ProGate.swift
|
|
// SportsTime
|
|
//
|
|
// View modifier that gates Pro-only features.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct ProGateModifier: ViewModifier {
|
|
let feature: ProFeature
|
|
|
|
@State private var showPaywall = false
|
|
|
|
func body(content: Content) -> some View {
|
|
content
|
|
.onTapGesture {
|
|
if !StoreManager.shared.isPro {
|
|
showPaywall = true
|
|
}
|
|
}
|
|
.allowsHitTesting(!StoreManager.shared.isPro ? true : true)
|
|
.overlay {
|
|
if !StoreManager.shared.isPro {
|
|
Color.clear
|
|
.contentShape(Rectangle())
|
|
.onTapGesture {
|
|
showPaywall = true
|
|
}
|
|
}
|
|
}
|
|
.sheet(isPresented: $showPaywall) {
|
|
PaywallView()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Modifier for buttons that should show paywall when tapped by free users
|
|
struct ProGateButtonModifier: ViewModifier {
|
|
let feature: ProFeature
|
|
let action: () -> Void
|
|
|
|
@State private var showPaywall = false
|
|
|
|
func body(content: Content) -> some View {
|
|
Button {
|
|
if StoreManager.shared.isPro {
|
|
action()
|
|
} else {
|
|
showPaywall = true
|
|
}
|
|
} label: {
|
|
content
|
|
}
|
|
.sheet(isPresented: $showPaywall) {
|
|
PaywallView()
|
|
}
|
|
}
|
|
}
|
|
|
|
extension View {
|
|
/// Gates entire view - tapping shows paywall if not Pro
|
|
func proGate(feature: ProFeature) -> some View {
|
|
modifier(ProGateModifier(feature: feature))
|
|
}
|
|
|
|
/// Gates a button action - shows paywall instead of performing action if not Pro
|
|
func proGateButton(feature: ProFeature, action: @escaping () -> Void) -> some View {
|
|
modifier(ProGateButtonModifier(feature: feature, action: action))
|
|
}
|
|
}
|