feat(store): add In-App Purchase system with Pro subscription
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>
This commit is contained in:
@@ -9,9 +9,13 @@ struct SettingsView: View {
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@State private var viewModel = SettingsViewModel()
|
||||
@State private var showResetConfirmation = false
|
||||
@State private var showPaywall = false
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
// Subscription
|
||||
subscriptionSection
|
||||
|
||||
// Theme Selection
|
||||
themeSection
|
||||
|
||||
@@ -187,6 +191,83 @@ struct SettingsView: View {
|
||||
.listRowBackground(Theme.cardBackground(colorScheme))
|
||||
}
|
||||
|
||||
// MARK: - Subscription Section
|
||||
|
||||
private var subscriptionSection: some View {
|
||||
Section {
|
||||
if StoreManager.shared.isPro {
|
||||
// Pro user - show manage option
|
||||
HStack {
|
||||
Label {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("SportsTime Pro")
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
Text("Active subscription")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.green)
|
||||
}
|
||||
} icon: {
|
||||
Image(systemName: "star.fill")
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundStyle(.green)
|
||||
}
|
||||
|
||||
Button {
|
||||
if let url = URL(string: "https://apps.apple.com/account/subscriptions") {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
} label: {
|
||||
Label("Manage Subscription", systemImage: "gear")
|
||||
}
|
||||
} else {
|
||||
// Free user - show upgrade option
|
||||
Button {
|
||||
showPaywall = true
|
||||
} label: {
|
||||
HStack {
|
||||
Label {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("Upgrade to Pro")
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
Text("Unlimited trips, PDF export, progress tracking")
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
} icon: {
|
||||
Image(systemName: "star.fill")
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "chevron.right")
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
|
||||
Button {
|
||||
Task {
|
||||
await StoreManager.shared.restorePurchases()
|
||||
}
|
||||
} label: {
|
||||
Label("Restore Purchases", systemImage: "arrow.clockwise")
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("Subscription")
|
||||
}
|
||||
.listRowBackground(Theme.cardBackground(colorScheme))
|
||||
.sheet(isPresented: $showPaywall) {
|
||||
PaywallView()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func sportColor(for sport: Sport) -> Color {
|
||||
|
||||
Reference in New Issue
Block a user