// // 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)) }