feat: 订阅页面一半完成
This commit is contained in:
parent
7d40fe3203
commit
417e101333
314
wake/View/Credits/CreditsInfoCard.swift
Normal file
314
wake/View/Credits/CreditsInfoCard.swift
Normal file
@ -0,0 +1,314 @@
|
||||
//
|
||||
// CreditsInfoCard.swift
|
||||
// wake
|
||||
//
|
||||
// Created by fairclip on 2025/8/19.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - 积分类型枚举
|
||||
enum CreditType: String, CaseIterable {
|
||||
case daily = "Daily"
|
||||
case purchased = "Purchased"
|
||||
case bonus = "Bonus"
|
||||
case permanent = "Permanent"
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .daily:
|
||||
return "Daily Credits"
|
||||
case .purchased:
|
||||
return "Purchased Credits"
|
||||
case .bonus:
|
||||
return "Bonus Credits"
|
||||
case .permanent:
|
||||
return "Permanent Credits"
|
||||
}
|
||||
}
|
||||
|
||||
var icon: String {
|
||||
switch self {
|
||||
case .daily:
|
||||
return "calendar"
|
||||
case .purchased:
|
||||
return "creditcard"
|
||||
case .bonus:
|
||||
return "gift"
|
||||
case .permanent:
|
||||
return "infinity"
|
||||
}
|
||||
}
|
||||
|
||||
var color: Color {
|
||||
switch self {
|
||||
case .daily:
|
||||
return Theme.Colors.info
|
||||
case .purchased:
|
||||
return Theme.Colors.success
|
||||
case .bonus:
|
||||
return Theme.Colors.warning
|
||||
case .permanent:
|
||||
return Theme.Colors.primary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 积分信息数据模型
|
||||
struct CreditInfo {
|
||||
let type: CreditType
|
||||
let amount: Int
|
||||
let description: String
|
||||
}
|
||||
|
||||
// MARK: - 积分信息卡片组件
|
||||
struct CreditsInfoCard: View {
|
||||
let totalCredits: Int
|
||||
let creditBreakdown: [CreditInfo]
|
||||
let onInfoTap: (() -> Void)?
|
||||
let onDetailTap: (() -> Void)?
|
||||
|
||||
@State private var showBreakdown = false
|
||||
|
||||
init(
|
||||
totalCredits: Int,
|
||||
creditBreakdown: [CreditInfo] = [],
|
||||
onInfoTap: (() -> Void)? = nil,
|
||||
onDetailTap: (() -> Void)? = nil
|
||||
) {
|
||||
self.totalCredits = totalCredits
|
||||
self.creditBreakdown = creditBreakdown
|
||||
self.onInfoTap = onInfoTap
|
||||
self.onDetailTap = onDetailTap
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
// 主要积分显示区域
|
||||
mainCreditsSection
|
||||
|
||||
// 积分明细展开区域
|
||||
if showBreakdown && !creditBreakdown.isEmpty {
|
||||
creditsBreakdownSection
|
||||
}
|
||||
}
|
||||
.background(Color(.systemBackground))
|
||||
.cornerRadius(Theme.CornerRadius.medium)
|
||||
.shadow(color: Theme.Shadows.small, radius: Theme.Shadows.cardShadow.radius, x: Theme.Shadows.cardShadow.x, y: Theme.Shadows.cardShadow.y)
|
||||
}
|
||||
|
||||
// MARK: - 主要积分显示区域
|
||||
private var mainCreditsSection: some View {
|
||||
HStack(spacing: Theme.Spacing.lg) {
|
||||
// 积分图标和数量
|
||||
HStack(spacing: Theme.Spacing.sm) {
|
||||
Circle()
|
||||
.fill(Theme.Gradients.primaryGradient)
|
||||
.frame(width: 40, height: 40)
|
||||
.overlay(
|
||||
Image(systemName: "star.fill")
|
||||
.foregroundColor(.white)
|
||||
.font(.system(size: 18, weight: .semibold))
|
||||
)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("Credits")
|
||||
.font(Typography.font(for: .caption, family: .quicksand))
|
||||
.foregroundColor(Theme.Colors.textSecondary)
|
||||
|
||||
Text("\(totalCredits)")
|
||||
.font(Typography.font(for: .title, family: .quicksandBold))
|
||||
.foregroundColor(Theme.Colors.textPrimary)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
// 操作按钮区域
|
||||
HStack(spacing: Theme.Spacing.sm) {
|
||||
// 信息按钮
|
||||
Button(action: {
|
||||
onInfoTap?()
|
||||
}) {
|
||||
Image(systemName: "info.circle")
|
||||
.foregroundColor(Theme.Colors.textSecondary)
|
||||
.font(.system(size: 16))
|
||||
}
|
||||
|
||||
// 展开/收起按钮
|
||||
if !creditBreakdown.isEmpty {
|
||||
Button(action: {
|
||||
withAnimation(.easeInOut(duration: 0.3)) {
|
||||
showBreakdown.toggle()
|
||||
}
|
||||
}) {
|
||||
Image(systemName: showBreakdown ? "chevron.up" : "chevron.down")
|
||||
.foregroundColor(Theme.Colors.textSecondary)
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
}
|
||||
}
|
||||
|
||||
// 详情按钮
|
||||
Button(action: {
|
||||
onDetailTap?()
|
||||
}) {
|
||||
Image(systemName: "chevron.right")
|
||||
.foregroundColor(Theme.Colors.textSecondary)
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(Theme.Spacing.lg)
|
||||
}
|
||||
|
||||
// MARK: - 积分明细展开区域
|
||||
private var creditsBreakdownSection: some View {
|
||||
VStack(spacing: 0) {
|
||||
Divider()
|
||||
.background(Theme.Colors.border)
|
||||
|
||||
VStack(spacing: Theme.Spacing.sm) {
|
||||
ForEach(Array(creditBreakdown.enumerated()), id: \.offset) { index, credit in
|
||||
CreditBreakdownRow(credit: credit, isLast: index == creditBreakdown.count - 1)
|
||||
}
|
||||
}
|
||||
.padding(Theme.Spacing.lg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 积分明细行组件
|
||||
struct CreditBreakdownRow: View {
|
||||
let credit: CreditInfo
|
||||
let isLast: Bool
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: Theme.Spacing.md) {
|
||||
// 积分类型图标
|
||||
Circle()
|
||||
.fill(credit.type.color.opacity(0.1))
|
||||
.frame(width: 32, height: 32)
|
||||
.overlay(
|
||||
Image(systemName: credit.type.icon)
|
||||
.foregroundColor(credit.type.color)
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
)
|
||||
|
||||
// 积分信息
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(credit.type.displayName)
|
||||
.font(Typography.font(for: .subtitle, family: .quicksand))
|
||||
.foregroundColor(Theme.Colors.textPrimary)
|
||||
|
||||
Text(credit.description)
|
||||
.font(Typography.font(for: .caption, family: .quicksand))
|
||||
.foregroundColor(Theme.Colors.textSecondary)
|
||||
.lineLimit(2)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
// 积分数量
|
||||
Text("+\(credit.amount)")
|
||||
.font(Typography.font(for: .body, family: .quicksandBold))
|
||||
.foregroundColor(credit.type.color)
|
||||
}
|
||||
.padding(.vertical, Theme.Spacing.xs)
|
||||
|
||||
if !isLast {
|
||||
Divider()
|
||||
.background(Theme.Colors.borderLight)
|
||||
.padding(.leading, 44)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 积分使用统计组件
|
||||
struct CreditsUsageCard: View {
|
||||
let todayUsed: Int
|
||||
let weeklyUsed: Int
|
||||
let monthlyUsed: Int
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: Theme.Spacing.md) {
|
||||
HStack {
|
||||
Text("Credits Usage")
|
||||
.font(Typography.font(for: .subtitle, family: .quicksandBold))
|
||||
.foregroundColor(Theme.Colors.textPrimary)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("This Period")
|
||||
.font(Typography.font(for: .caption, family: .quicksand))
|
||||
.foregroundColor(Theme.Colors.textSecondary)
|
||||
}
|
||||
|
||||
HStack(spacing: Theme.Spacing.lg) {
|
||||
UsageStatItem(title: "Today", value: todayUsed, color: Theme.Colors.info)
|
||||
|
||||
Divider()
|
||||
.frame(height: 40)
|
||||
|
||||
UsageStatItem(title: "Week", value: weeklyUsed, color: Theme.Colors.warning)
|
||||
|
||||
Divider()
|
||||
.frame(height: 40)
|
||||
|
||||
UsageStatItem(title: "Month", value: monthlyUsed, color: Theme.Colors.success)
|
||||
}
|
||||
}
|
||||
.padding(Theme.Spacing.lg)
|
||||
.background(Color(.systemBackground))
|
||||
.cornerRadius(Theme.CornerRadius.medium)
|
||||
.shadow(color: Theme.Shadows.small, radius: Theme.Shadows.cardShadow.radius, x: Theme.Shadows.cardShadow.x, y: Theme.Shadows.cardShadow.y)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 使用统计项组件
|
||||
struct UsageStatItem: View {
|
||||
let title: String
|
||||
let value: Int
|
||||
let color: Color
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: Theme.Spacing.xs) {
|
||||
Text("\(value)")
|
||||
.font(Typography.font(for: .title, family: .quicksandBold))
|
||||
.foregroundColor(color)
|
||||
|
||||
Text(title)
|
||||
.font(Typography.font(for: .caption, family: .quicksand))
|
||||
.foregroundColor(Theme.Colors.textSecondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 预览
|
||||
#Preview("Credits Info Card") {
|
||||
VStack(spacing: 20) {
|
||||
CreditsInfoCard(
|
||||
totalCredits: 3290,
|
||||
creditBreakdown: [
|
||||
CreditInfo(type: .daily, amount: 200, description: "Daily free credits"),
|
||||
CreditInfo(type: .purchased, amount: 1000, description: "Purchased package"),
|
||||
CreditInfo(type: .bonus, amount: 500, description: "Welcome bonus"),
|
||||
CreditInfo(type: .permanent, amount: 1590, description: "Subscription credits")
|
||||
],
|
||||
onInfoTap: {
|
||||
print("Info tapped")
|
||||
},
|
||||
onDetailTap: {
|
||||
print("Detail tapped")
|
||||
}
|
||||
)
|
||||
|
||||
CreditsUsageCard(
|
||||
todayUsed: 45,
|
||||
weeklyUsed: 280,
|
||||
monthlyUsed: 1150
|
||||
)
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.systemGroupedBackground))
|
||||
}
|
||||
0
wake/View/Subscribe/Components/PlanSelector.swift
Normal file
0
wake/View/Subscribe/Components/PlanSelector.swift
Normal file
Loading…
x
Reference in New Issue
Block a user