feat: navi header
This commit is contained in:
parent
10e9324049
commit
f361d73bf7
BIN
wake.xcodeproj/project.xcworkspace/xcuserdata/fairclip.xcuserdatad/UserInterfaceState.xcuserstate
generated
Normal file
BIN
wake.xcodeproj/project.xcworkspace/xcuserdata/fairclip.xcuserdatad/UserInterfaceState.xcuserstate
generated
Normal file
Binary file not shown.
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>SchemeUserState</key>
|
||||||
|
<dict>
|
||||||
|
<key>wake.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
BIN
wake/.DS_Store
vendored
BIN
wake/.DS_Store
vendored
Binary file not shown.
121
wake/Components/Buttons/ReturnButton.swift
Normal file
121
wake/Components/Buttons/ReturnButton.swift
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
//
|
||||||
|
// ReturnButton.swift
|
||||||
|
// wake
|
||||||
|
//
|
||||||
|
// Created by Junhui on 2025/8/19.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
/// 返回按钮组件,用于左上角导航返回
|
||||||
|
struct ReturnButton: View {
|
||||||
|
let action: () -> Void
|
||||||
|
var iconName: String = "chevron.left"
|
||||||
|
var iconSize: TypographyStyle = .title
|
||||||
|
var iconColor: Color = .primary
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button(action: action) {
|
||||||
|
Image(systemName: iconName)
|
||||||
|
.font(Typography.font(for: iconSize))
|
||||||
|
.fontWeight(.medium)
|
||||||
|
.foregroundColor(iconColor)
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 带文字的返回按钮组件
|
||||||
|
struct ReturnButtonWithText: View {
|
||||||
|
let action: () -> Void
|
||||||
|
let text: String
|
||||||
|
var iconName: String = "chevron.left"
|
||||||
|
var spacing: CGFloat = 4
|
||||||
|
var textStyle: TypographyStyle = .body
|
||||||
|
var iconColor: Color = .primary
|
||||||
|
var textColor: Color = .primary
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button(action: action) {
|
||||||
|
HStack(spacing: spacing) {
|
||||||
|
Image(systemName: iconName)
|
||||||
|
.font(Typography.font(for: textStyle, family: .quicksandRegular))
|
||||||
|
.fontWeight(.medium)
|
||||||
|
.foregroundColor(iconColor)
|
||||||
|
|
||||||
|
Text(text)
|
||||||
|
.font(Typography.font(for: textStyle, family: .quicksandRegular))
|
||||||
|
.foregroundColor(textColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 圆形背景的返回按钮组件
|
||||||
|
struct CircularReturnButton: View {
|
||||||
|
let action: () -> Void
|
||||||
|
var iconName: String = "chevron.left"
|
||||||
|
var size: CGFloat = 40
|
||||||
|
var backgroundColor: Color = Color(.systemBackground)
|
||||||
|
var iconColor: Color = .primary
|
||||||
|
var shadowRadius: CGFloat = 4
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button(action: action) {
|
||||||
|
Image(systemName: iconName)
|
||||||
|
.font(Typography.font(for: .body, family: .quicksandRegular))
|
||||||
|
.foregroundColor(iconColor)
|
||||||
|
.frame(width: size, height: size)
|
||||||
|
.background(backgroundColor)
|
||||||
|
.clipShape(Circle())
|
||||||
|
.shadow(color: Color.black.opacity(0.1), radius: shadowRadius, x: 0, y: 2)
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview("基础返回按钮") {
|
||||||
|
VStack(spacing: 20) {
|
||||||
|
HStack {
|
||||||
|
ReturnButton {
|
||||||
|
print("返回")
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.background(Color(.systemGroupedBackground))
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview("带文字返回按钮") {
|
||||||
|
VStack(spacing: 20) {
|
||||||
|
HStack {
|
||||||
|
ReturnButtonWithText(action: {
|
||||||
|
print("返回")
|
||||||
|
}, text: "Back")
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.background(Color(.systemGroupedBackground))
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview("圆形返回按钮") {
|
||||||
|
VStack(spacing: 20) {
|
||||||
|
HStack {
|
||||||
|
CircularReturnButton {
|
||||||
|
print("返回")
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.background(Color(.systemGroupedBackground))
|
||||||
|
}
|
||||||
163
wake/Components/Navi/NaviHeader.swift
Normal file
163
wake/Components/Navi/NaviHeader.swift
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
//
|
||||||
|
// NaviHeader.swift
|
||||||
|
// wake
|
||||||
|
//
|
||||||
|
// Created by Junhui on 2025/8/19.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
/// 导航头组件,包含返回按钮和标题
|
||||||
|
struct NaviHeader: View {
|
||||||
|
let title: String
|
||||||
|
let onBackTap: () -> Void
|
||||||
|
var showBackButton: Bool = true
|
||||||
|
var titleStyle: TypographyStyle = .title
|
||||||
|
var backgroundColor: Color = Color.clear
|
||||||
|
var rightContent: AnyView? = nil
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
// 标题居中显示
|
||||||
|
Text(title)
|
||||||
|
.font(Typography.font(for: titleStyle, family: .quicksandBold))
|
||||||
|
.fontWeight(.bold)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
|
||||||
|
// 左右按钮层
|
||||||
|
HStack {
|
||||||
|
// 左侧返回按钮
|
||||||
|
if showBackButton {
|
||||||
|
ReturnButton(action: onBackTap)
|
||||||
|
} else {
|
||||||
|
Color.clear
|
||||||
|
.frame(width: 30)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
// 右侧内容
|
||||||
|
if let rightContent = rightContent {
|
||||||
|
rightContent
|
||||||
|
} else {
|
||||||
|
Color.clear
|
||||||
|
.frame(width: 30)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.top, 10)
|
||||||
|
.padding(.bottom, 20)
|
||||||
|
.background(backgroundColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 带右侧按钮的导航头组件
|
||||||
|
struct NaviHeaderWithAction: View {
|
||||||
|
let title: String
|
||||||
|
let onBackTap: () -> Void
|
||||||
|
let rightButtonTitle: String
|
||||||
|
let onRightButtonTap: () -> Void
|
||||||
|
var showBackButton: Bool = true
|
||||||
|
var titleStyle: TypographyStyle = .title
|
||||||
|
var rightButtonStyle: TypographyStyle = .body
|
||||||
|
var backgroundColor: Color = Color.clear
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
// 标题居中显示
|
||||||
|
Text(title)
|
||||||
|
.font(Typography.font(for: titleStyle, family: .quicksandBold))
|
||||||
|
.fontWeight(.bold)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
|
||||||
|
// 左右按钮层
|
||||||
|
HStack {
|
||||||
|
// 左侧返回按钮
|
||||||
|
if showBackButton {
|
||||||
|
ReturnButton(action: onBackTap)
|
||||||
|
} else {
|
||||||
|
Color.clear
|
||||||
|
.frame(width: 30)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
// 右侧按钮
|
||||||
|
Button(action: onRightButtonTap) {
|
||||||
|
Text(rightButtonTitle)
|
||||||
|
.font(Typography.font(for: rightButtonStyle, family: .quicksandBold))
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundColor(.blue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.top, 10)
|
||||||
|
.padding(.bottom, 20)
|
||||||
|
.background(backgroundColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 简洁版导航头组件
|
||||||
|
struct SimpleNaviHeader: View {
|
||||||
|
let title: String
|
||||||
|
let onBackTap: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
// 标题居中显示
|
||||||
|
Text(title)
|
||||||
|
.font(Typography.font(for: .title, family: .quicksandBold))
|
||||||
|
.fontWeight(.bold)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
|
||||||
|
// 返回按钮左对齐
|
||||||
|
HStack {
|
||||||
|
ReturnButton(action: onBackTap)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.vertical, 16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview("基础导航头") {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
NaviHeader(title: "Settings") {
|
||||||
|
print("返回")
|
||||||
|
}
|
||||||
|
.background(Color(.systemBackground))
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.background(Color(.systemGroupedBackground))
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview("带右侧按钮导航头") {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
NaviHeaderWithAction(
|
||||||
|
title: "Profile",
|
||||||
|
onBackTap: { print("返回") },
|
||||||
|
rightButtonTitle: "Save",
|
||||||
|
onRightButtonTap: { print("保存") }
|
||||||
|
)
|
||||||
|
.background(Color(.systemBackground))
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.background(Color(.systemGroupedBackground))
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview("简洁导航头") {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
SimpleNaviHeader(title: "About") {
|
||||||
|
print("返回")
|
||||||
|
}
|
||||||
|
.background(Color(.systemBackground))
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.background(Color(.systemGroupedBackground))
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@ enum FontFamily: String, CaseIterable {
|
|||||||
case sankeiCute = "SankeiCutePopanime" // 可爱风格字体
|
case sankeiCute = "SankeiCutePopanime" // 可爱风格字体
|
||||||
case quicksand = "Quicksand x" // 主题字体
|
case quicksand = "Quicksand x" // 主题字体
|
||||||
case quicksandBold = "Quicksand-Bold"
|
case quicksandBold = "Quicksand-Bold"
|
||||||
|
case quicksandRegular = "Quicksand-Regular"
|
||||||
// 后续添加新字体库时在这里添加新 case
|
// 后续添加新字体库时在这里添加新 case
|
||||||
// 例如: case anotherFont = "AnotherFontName"
|
// 例如: case anotherFont = "AnotherFontName"
|
||||||
|
|
||||||
|
|||||||
389
wake/View/Subscribe/SubscribeView.swift
Normal file
389
wake/View/Subscribe/SubscribeView.swift
Normal file
@ -0,0 +1,389 @@
|
|||||||
|
//
|
||||||
|
// SubscribeView.swift
|
||||||
|
// wake
|
||||||
|
//
|
||||||
|
// Created by fairclip on 2025/8/19.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
@State private var selectedPlan: SubscriptionPlan = .free
|
||||||
|
@State private var isLoading = false
|
||||||
|
@Environment(\.presentationMode) var presentationMode
|
||||||
|
|
||||||
|
// 功能对比数据
|
||||||
|
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 {
|
||||||
|
NavigationView {
|
||||||
|
ScrollView {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
// 导航栏
|
||||||
|
navigationHeader
|
||||||
|
|
||||||
|
// 当前订阅状态卡片
|
||||||
|
currentSubscriptionCard
|
||||||
|
|
||||||
|
// 积分信息
|
||||||
|
creditsSection
|
||||||
|
|
||||||
|
// 订阅计划选择
|
||||||
|
subscriptionPlansSection
|
||||||
|
|
||||||
|
// 特别优惠提示
|
||||||
|
specialOfferBanner
|
||||||
|
|
||||||
|
// 功能对比表
|
||||||
|
featureComparisonTable
|
||||||
|
|
||||||
|
// 订阅按钮
|
||||||
|
subscribeButton
|
||||||
|
|
||||||
|
// 法律链接
|
||||||
|
legalLinks
|
||||||
|
|
||||||
|
Spacer(minLength: 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.background(Color(.systemGroupedBackground))
|
||||||
|
.navigationBarHidden(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 导航栏
|
||||||
|
private var navigationHeader: some View {
|
||||||
|
NaviHeader(title: "Subscription") {
|
||||||
|
presentationMode.wrappedValue.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 当前订阅状态卡片
|
||||||
|
private var currentSubscriptionCard: some View {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
HStack {
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
Text("Free")
|
||||||
|
.font(Typography.font(for: .headline, family: .quicksand))
|
||||||
|
.fontWeight(.bold)
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
// 订阅操作
|
||||||
|
}) {
|
||||||
|
Text("Subscribe")
|
||||||
|
.font(Typography.font(for: .subtitle, family: .quicksand))
|
||||||
|
.fontWeight(.medium)
|
||||||
|
.foregroundColor(.black)
|
||||||
|
.padding(.horizontal, 16)
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
.background(Color.orange)
|
||||||
|
.cornerRadius(20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
// 播放按钮图标
|
||||||
|
Circle()
|
||||||
|
.fill(Color.black)
|
||||||
|
.frame(width: 60, height: 60)
|
||||||
|
.overlay(
|
||||||
|
Image(systemName: "play.fill")
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.font(.title2)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.padding(20)
|
||||||
|
.background(Color.orange.opacity(0.2))
|
||||||
|
.cornerRadius(16)
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 积分信息
|
||||||
|
private var creditsSection: some View {
|
||||||
|
HStack {
|
||||||
|
Text("Credits: 3290")
|
||||||
|
.font(Typography.font(for: .body, family: .quicksand))
|
||||||
|
.fontWeight(.medium)
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
// 积分信息操作
|
||||||
|
}) {
|
||||||
|
Image(systemName: "info.circle")
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Image(systemName: "chevron.right")
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.font(.caption)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.vertical, 16)
|
||||||
|
.background(Color(.systemBackground))
|
||||||
|
.cornerRadius(12)
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.top, 20)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 订阅计划选择
|
||||||
|
private var subscriptionPlansSection: some View {
|
||||||
|
HStack(spacing: 16) {
|
||||||
|
// Free 计划
|
||||||
|
SubscriptionPlanCard(
|
||||||
|
plan: .free,
|
||||||
|
isSelected: selectedPlan == .free,
|
||||||
|
onTap: { selectedPlan = .free }
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pioneer 计划
|
||||||
|
SubscriptionPlanCard(
|
||||||
|
plan: .pioneer,
|
||||||
|
isSelected: selectedPlan == .pioneer,
|
||||||
|
onTap: { selectedPlan = .pioneer }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.top, 20)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 特别优惠横幅
|
||||||
|
private var specialOfferBanner: some View {
|
||||||
|
Text("First 100 users get a special deal: just $1 for your first month!")
|
||||||
|
.font(Typography.font(for: .caption, family: .quicksand))
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.top, 12)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 功能对比表
|
||||||
|
private var featureComparisonTable: some View {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
// 表头
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Text("Free")
|
||||||
|
.font(Typography.font(for: .subtitle, family: .quicksand))
|
||||||
|
.fontWeight(.medium)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
Text("Pro")
|
||||||
|
.font(Typography.font(for: .subtitle, family: .quicksand))
|
||||||
|
.fontWeight(.medium)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 16)
|
||||||
|
.background(Color(.systemGray6))
|
||||||
|
|
||||||
|
// 功能行
|
||||||
|
ForEach(Array(features.enumerated()), id: \.offset) { index, feature in
|
||||||
|
FeatureRow(feature: feature, isLast: index == features.count - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.background(Color(.systemBackground))
|
||||||
|
.cornerRadius(12)
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.top, 20)
|
||||||
|
.shadow(color: Color.black.opacity(0.05), radius: 2, x: 0, y: 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 订阅按钮
|
||||||
|
private var subscribeButton: some View {
|
||||||
|
VStack(spacing: 12) {
|
||||||
|
Button(action: {
|
||||||
|
handleSubscribe()
|
||||||
|
}) {
|
||||||
|
if isLoading {
|
||||||
|
HStack {
|
||||||
|
ProgressView()
|
||||||
|
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
||||||
|
.scaleEffect(0.8)
|
||||||
|
Text("Subscribe")
|
||||||
|
.font(Typography.font(for: .body, family: .quicksand))
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text("Subscribe")
|
||||||
|
.font(Typography.font(for: .body, family: .quicksand))
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding(.vertical, 16)
|
||||||
|
.background(Color.blue)
|
||||||
|
.cornerRadius(25)
|
||||||
|
.disabled(isLoading)
|
||||||
|
|
||||||
|
Text("Get 5,000 Permanent Credits")
|
||||||
|
.font(Typography.font(for: .caption, family: .quicksand))
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.top, 30)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 法律链接
|
||||||
|
private var legalLinks: some View {
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
Button("Terms of Service") {
|
||||||
|
// 打开服务条款
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("|")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
|
||||||
|
Button("Privacy Policy") {
|
||||||
|
// 打开隐私政策
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("|")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
|
||||||
|
Button("Restore Purchase") {
|
||||||
|
// 恢复购买
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.font(Typography.font(for: .caption, family: .quicksand))
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.padding(.top, 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 订阅处理
|
||||||
|
private func handleSubscribe() {
|
||||||
|
isLoading = true
|
||||||
|
|
||||||
|
// 模拟订阅处理
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
||||||
|
isLoading = false
|
||||||
|
// 处理订阅逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 订阅计划卡片
|
||||||
|
struct SubscriptionPlanCard: View {
|
||||||
|
let plan: SubscriptionPlan
|
||||||
|
let isSelected: Bool
|
||||||
|
let onTap: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button(action: onTap) {
|
||||||
|
VStack(spacing: 12) {
|
||||||
|
if plan.isPopular {
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Text("Popular")
|
||||||
|
.font(Typography.font(for: .caption, family: .quicksand))
|
||||||
|
.fontWeight(.medium)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.padding(.horizontal, 12)
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
.background(Color.black)
|
||||||
|
.cornerRadius(12)
|
||||||
|
}
|
||||||
|
.padding(.top, -8)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(plan.displayName)
|
||||||
|
.font(Typography.font(for: .title, family: .quicksand))
|
||||||
|
.fontWeight(.bold)
|
||||||
|
.foregroundColor(plan == .pioneer ? .white : .gray)
|
||||||
|
|
||||||
|
Text(plan.price)
|
||||||
|
.font(Typography.font(for: .body, family: .quicksand))
|
||||||
|
.fontWeight(.medium)
|
||||||
|
.foregroundColor(plan == .pioneer ? .white : .gray)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, minHeight: 120)
|
||||||
|
.padding(16)
|
||||||
|
.background(plan == .pioneer ? Color.orange : Color.white)
|
||||||
|
.cornerRadius(16)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 16)
|
||||||
|
.stroke(isSelected ? Color.blue : (plan == .free ? Color.blue : Color.clear), lineWidth: 2)
|
||||||
|
)
|
||||||
|
.shadow(color: Color.black.opacity(0.1), radius: 4, x: 0, y: 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 功能对比行
|
||||||
|
struct FeatureRow: View {
|
||||||
|
let feature: SubscriptionFeature
|
||||||
|
let isLast: Bool
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
Text(feature.name)
|
||||||
|
.font(Typography.font(for: .subtitle, family: .quicksand))
|
||||||
|
.fontWeight(.medium)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
|
||||||
|
Text(feature.freeValue)
|
||||||
|
.font(Typography.font(for: .caption, family: .quicksand))
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
|
||||||
|
Text(feature.proValue)
|
||||||
|
.font(Typography.font(for: .caption, family: .quicksand))
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 12)
|
||||||
|
.padding(.horizontal, 16)
|
||||||
|
.background(Color(.systemBackground))
|
||||||
|
|
||||||
|
if !isLast {
|
||||||
|
Divider()
|
||||||
|
.padding(.leading, 16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
SubscribeView()
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user