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}