krz/domain-dig
an ios app for DNS & SSL analysis
clone: git clone https://gitbay.org/krz/domain-dig.git
v5.0.1: DomainDig/PaywallView.swift · raw
1import StoreKit
2import SwiftUI
3
4struct PaywallView: View {
5 @Environment(\.dismiss) private var dismiss
6 @Environment(\.appDensity) private var appDensity
7 @State private var purchaseService = PurchaseService.shared
8
9 var body: some View {
10 NavigationStack {
11 List {
12 Section {
13 Text("Pro unlocks workflows, scale, monitoring automation, and exports. Pro+ adds deeper external intelligence with no additional usage limits.")
14 .font(appDensity.font(.body, design: .default))
15 .foregroundStyle(Color(.appTextSecondary))
16 .fixedSize(horizontal: false, vertical: true)
17 }
18
19 Section("What Pro Unlocks") {
20 featureRow("Unlimited tracked domains")
21 featureRow("Workflows")
22 featureRow("Larger batch sizes")
23 featureRow("Background monitoring")
24 featureRow("Local alerts")
25 featureRow("Advanced exports")
26 }
27
28 Section("What Pro+ Unlocks") {
29 featureRow("Ownership history")
30 featureRow("DNS history")
31 featureRow("Extended subdomains")
32 featureRow("External pricing signals")
33 }
34
35 Section("Subscription") {
36 if purchaseService.isLoadingProducts {
37 ProgressView("Loading pricing…")
38 } else if purchaseService.products.isEmpty {
39 Text("Pricing is unavailable right now.")
40 .font(appDensity.font(.caption, design: .default))
41 .foregroundStyle(Color(.appTextSecondary))
42
43 Button("Retry") {
44 Task {
45 await purchaseService.refreshProducts()
46 }
47 }
48 } else {
49 ForEach(purchaseService.products, id: \.id) { product in
50 Button {
51 Task {
52 await purchaseService.purchase(product)
53 }
54 } label: {
55 VStack(alignment: .leading, spacing: 6) {
56 Text(subscriptionTitle(for: product))
57 .font(appDensity.font(.headline, design: .default, weight: .semibold))
58 .foregroundStyle(.primary)
59 Text(product.displayPrice)
60 .font(appDensity.font(.callout, design: .default))
61 .foregroundStyle(Color(.appTextSecondary))
62 }
63 .frame(maxWidth: .infinity, alignment: .leading)
64 }
65 .disabled(purchaseService.isPurchasing || purchaseService.isRestoring)
66 }
67 }
68
69 if let statusMessage = purchaseService.statusMessage {
70 Text(statusMessage)
71 .font(appDensity.font(.caption, design: .default))
72 .foregroundStyle(Color(.appTextSecondary))
73 }
74
75 if let errorMessage = purchaseService.errorMessage {
76 Text(errorMessage)
77 .font(appDensity.font(.caption, design: .default))
78 .foregroundStyle(Color(.statusCritical))
79 }
80 }
81
82 Section("Account") {
83 Button(purchaseService.isRestoring ? "Restoring…" : "Restore Purchases") {
84 Task {
85 await purchaseService.restorePurchases()
86 }
87 }
88 .disabled(purchaseService.isPurchasing || purchaseService.isRestoring)
89
90 if purchaseService.hasProAccess {
91 Button("Manage Subscription") {
92 Task {
93 await purchaseService.manageSubscription()
94 }
95 }
96 }
97 }
98 }
99 .navigationTitle("DomainDig Pro")
100 .toolbar {
101 ToolbarItem(placement: .topBarTrailing) {
102 Button("Done") {
103 dismiss()
104 }
105 }
106 }
107 }
108 .task {
109 await purchaseService.refreshProducts()
110 await purchaseService.refreshEntitlements()
111 }
112 .onDisappear {
113 purchaseService.clearMessages()
114 }
115 }
116
117 private func featureRow(_ title: String) -> some View {
118 Text(title)
119 .font(appDensity.font(.body, design: .default))
120 }
121
122 private func subscriptionTitle(for product: Product) -> String {
123 switch product.id {
124 case PurchaseService.monthlyProductID:
125 return "Pro Monthly"
126 case PurchaseService.yearlyProductID:
127 return "Pro Yearly"
128 case PurchaseService.proPlusMonthlyProductID:
129 return "Pro+ Monthly"
130 case PurchaseService.proPlusYearlyProductID:
131 return "Pro+ Yearly"
132 default:
133 return product.displayName
134 }
135 }
136}