265 lines
7.8 KiB
Swift
265 lines
7.8 KiB
Swift
//
|
||
// SubscribeView.swift
|
||
// wake
|
||
//
|
||
// Created by fairclip on 2025/8/19.
|
||
//
|
||
|
||
import SwiftUI
|
||
import StoreKit
|
||
|
||
// MARK: - 订阅计划枚举
|
||
enum SubscriptionPlan: String, CaseIterable {
|
||
case free = "Free"
|
||
case pioneer = "Pioneer"
|
||
|
||
var displayName: String {
|
||
return self.rawValue
|
||
}
|
||
|
||
var price: String {
|
||
switch self {
|
||
case .free:
|
||
return "Free"
|
||
case .pioneer:
|
||
return "1$/Mon"
|
||
}
|
||
}
|
||
|
||
var isPopular: Bool {
|
||
return self == .pioneer
|
||
}
|
||
}
|
||
|
||
// MARK: - 功能特性结构体
|
||
struct SubscriptionFeature {
|
||
let name: String
|
||
let freeValue: String
|
||
let proValue: String
|
||
}
|
||
|
||
struct SubscribeView: View {
|
||
@Environment(\.dismiss) private var dismiss
|
||
@StateObject private var store = IAPManager()
|
||
@State private var selectedPlan: SubscriptionPlan? = .pioneer
|
||
@State private var isLoading = false
|
||
@State private var showErrorAlert = false
|
||
@State private var errorText = ""
|
||
|
||
|
||
// 功能对比数据
|
||
private let features = [
|
||
SubscriptionFeature(name: "Mystery Box Purchase:", freeValue: "3 /week", proValue: "Free"),
|
||
SubscriptionFeature(name: "Material Upload:", freeValue: "50 images and\n5 videos/day", proValue: "Unlimited"),
|
||
SubscriptionFeature(name: "Free Credits:", freeValue: "200 /day", proValue: "500 /day")
|
||
]
|
||
|
||
var body: some View {
|
||
VStack(spacing: 0) {
|
||
// 自定义简洁导航头,统一风格
|
||
SimpleNaviHeader(title: "Subscription") {
|
||
dismiss()
|
||
}
|
||
|
||
ScrollView {
|
||
VStack(spacing: 0) {
|
||
// 当前订阅状态卡片
|
||
currentSubscriptionCard
|
||
|
||
// 积分信息
|
||
creditsSection
|
||
|
||
VStack {
|
||
// 订阅计划选择
|
||
subscriptionPlansSection
|
||
|
||
// 特别优惠提示
|
||
specialOfferBanner
|
||
}
|
||
.background(Theme.Colors.cardBackground)
|
||
.cornerRadius(Theme.CornerRadius.medium)
|
||
.padding(.horizontal, Theme.Spacing.lg)
|
||
.padding(.vertical, Theme.Spacing.lg)
|
||
|
||
|
||
// 功能对比表
|
||
featureComparisonTable
|
||
|
||
// 订阅按钮
|
||
subscribeButton
|
||
|
||
// 法律链接
|
||
legalLinks
|
||
|
||
Spacer(minLength: 100)
|
||
}
|
||
}
|
||
.background(Theme.Colors.background)
|
||
}
|
||
.navigationBarHidden(true)
|
||
.task {
|
||
// Load products and refresh current entitlements on appear
|
||
await store.loadProducts()
|
||
await store.refreshEntitlements()
|
||
}
|
||
.onChange(of: store.isPurchasing) { newValue in
|
||
// Bind purchasing state to button loading
|
||
isLoading = newValue
|
||
}
|
||
.onChange(of: store.errorMessage) { newValue in
|
||
if let message = newValue, !message.isEmpty {
|
||
errorText = message
|
||
showErrorAlert = true
|
||
}
|
||
}
|
||
.alert("Purchase Error", isPresented: $showErrorAlert) {
|
||
Button("OK", role: .cancel) { store.errorMessage = nil }
|
||
} message: {
|
||
Text(errorText)
|
||
}
|
||
}
|
||
|
||
// MARK: - 当前订阅状态卡片
|
||
private var currentSubscriptionCard: some View {
|
||
let status: SubscriptionStatus = {
|
||
if store.isSubscribed {
|
||
return .pioneer(expiryDate: store.subscriptionExpiry ?? Date())
|
||
} else {
|
||
return .free
|
||
}
|
||
}()
|
||
|
||
return SubscriptionStatusBar(
|
||
status: status,
|
||
onSubscribeTap: {
|
||
// 订阅操作
|
||
handleSubscribe()
|
||
}
|
||
)
|
||
.padding(.horizontal, Theme.Spacing.xl)
|
||
}
|
||
|
||
// MARK: - 积分信息
|
||
private var creditsSection: some View {
|
||
VStack(spacing: 16) {
|
||
CreditsInfoCard(
|
||
totalCredits: 3290,
|
||
onInfoTap: {
|
||
// 显示积分信息说明
|
||
},
|
||
onDetailTap: {
|
||
// 跳转到积分详情页面
|
||
}
|
||
)
|
||
|
||
}
|
||
.padding(.horizontal, Theme.Spacing.xl)
|
||
.padding(.top, Theme.Spacing.xl)
|
||
}
|
||
|
||
// MARK: - 订阅计划选择
|
||
private var subscriptionPlansSection: some View {
|
||
PlanSelector(
|
||
selectedPlan: $selectedPlan,
|
||
onPlanSelected: { plan in
|
||
print("Selected plan: \(plan.displayName)")
|
||
}
|
||
)
|
||
.padding(.horizontal, Theme.Spacing.xl)
|
||
.padding(.top, Theme.Spacing.xl)
|
||
}
|
||
|
||
// MARK: - 特别优惠横幅
|
||
private var specialOfferBanner: some View {
|
||
HStack(spacing: 0) {
|
||
Text("First")
|
||
.font(Typography.font(for: .footnote, family: .quicksandRegular))
|
||
.foregroundColor(Theme.Colors.textPrimary)
|
||
|
||
Text(" 100")
|
||
.font(Typography.font(for: .footnote, family: .quicksandBold))
|
||
.foregroundColor(Theme.Colors.textPrimary)
|
||
|
||
Text(" users get a special deal: justs")
|
||
.font(Typography.font(for: .footnote, family: .quicksandRegular))
|
||
.foregroundColor(Theme.Colors.textPrimary)
|
||
|
||
Text(" $1")
|
||
.font(Typography.font(for: .footnote, family: .quicksandBold))
|
||
.foregroundColor(Theme.Colors.textPrimary)
|
||
|
||
Text(" for your first month!")
|
||
.font(Typography.font(for: .footnote, family: .quicksandRegular))
|
||
.foregroundColor(Theme.Colors.textPrimary)
|
||
}
|
||
.multilineTextAlignment(.center)
|
||
.padding(.horizontal, Theme.Spacing.lg)
|
||
.padding(.top, Theme.Spacing.sm)
|
||
.padding(.bottom, Theme.Spacing.lg)
|
||
}
|
||
|
||
// MARK: - 功能对比表
|
||
private var featureComparisonTable: some View {
|
||
PlanCompare()
|
||
.padding(.horizontal, Theme.Spacing.lg)
|
||
}
|
||
|
||
// MARK: - 订阅按钮
|
||
private var subscribeButton: some View {
|
||
VStack(spacing: 12) {
|
||
SubscribeButton(
|
||
title: "Subscribe",
|
||
isLoading: isLoading,
|
||
subscribed: store.isSubscribed,
|
||
action: handleSubscribe
|
||
)
|
||
}
|
||
.padding(.horizontal, Theme.Spacing.xl)
|
||
.padding(.top, Theme.Spacing.lg)
|
||
}
|
||
|
||
// MARK: - 法律链接
|
||
private var legalLinks: some View {
|
||
HStack(spacing: 8) {
|
||
Button(action: {
|
||
// 打开服务条款
|
||
}) {
|
||
Text("Terms of Service")
|
||
.underline()
|
||
}
|
||
|
||
Text("|")
|
||
.foregroundColor(.secondary)
|
||
|
||
Button(action: {
|
||
// 打开隐私政策
|
||
}) {
|
||
Text("Privacy Policy")
|
||
.underline()
|
||
}
|
||
|
||
Text("|")
|
||
.foregroundColor(.secondary)
|
||
|
||
Button(action: {
|
||
Task { await store.restorePurchases() }
|
||
}) {
|
||
Text("Restore Purchase")
|
||
.underline()
|
||
}
|
||
}
|
||
.font(Typography.font(for: .caption, family: .quicksandRegular))
|
||
.foregroundColor(.secondary)
|
||
.padding(.top, Theme.Spacing.sm)
|
||
}
|
||
|
||
// MARK: - 订阅处理
|
||
private func handleSubscribe() {
|
||
Task { await store.purchasePioneer() }
|
||
}
|
||
}
|
||
|
||
#Preview {
|
||
SubscribeView()
|
||
}
|