289 lines
9.9 KiB
Swift
289 lines
9.9 KiB
Swift
//
|
|
// CreditsDetailView.swift
|
|
// wake
|
|
//
|
|
// Created by fairclip on 2025/8/19.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
// MARK: - 积分交易类型
|
|
enum CreditTransactionType: String, CaseIterable {
|
|
case photoUnderstanding = "Photo Understanding"
|
|
case videoUnderstanding = "Video Understanding"
|
|
case mysteryBoxPurchase = "Mystery Box Purchase"
|
|
case dailyBonus = "Daily Bonus"
|
|
case subscriptionBonus = "Subscription Bonus"
|
|
|
|
var creditChange: Int {
|
|
switch self {
|
|
case .photoUnderstanding:
|
|
return -1
|
|
case .videoUnderstanding:
|
|
return -32
|
|
case .mysteryBoxPurchase:
|
|
return -100
|
|
case .dailyBonus:
|
|
return 200
|
|
case .subscriptionBonus:
|
|
return 500
|
|
}
|
|
}
|
|
|
|
var icon: String {
|
|
switch self {
|
|
case .photoUnderstanding:
|
|
return "photo"
|
|
case .videoUnderstanding:
|
|
return "video"
|
|
case .mysteryBoxPurchase:
|
|
return "gift"
|
|
case .dailyBonus:
|
|
return "calendar"
|
|
case .subscriptionBonus:
|
|
return "star.fill"
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - 积分交易记录
|
|
struct CreditTransaction {
|
|
let id = UUID()
|
|
let type: CreditTransactionType
|
|
let date: Date
|
|
let creditChange: Int
|
|
|
|
init(type: CreditTransactionType, date: Date, creditChange: Int? = nil) {
|
|
self.type = type
|
|
self.date = date
|
|
self.creditChange = creditChange ?? type.creditChange
|
|
}
|
|
}
|
|
|
|
// MARK: - 积分详情页面
|
|
struct CreditsDetailView: View {
|
|
@Environment(\.presentationMode) var presentationMode
|
|
@State private var showRules = false
|
|
|
|
// 示例数据
|
|
private let totalCredits = 3290
|
|
private let expiringToday = 200
|
|
private let transactions: [CreditTransaction] = [
|
|
CreditTransaction(type: .photoUnderstanding, date: Calendar.current.date(byAdding: .hour, value: -2, to: Date()) ?? Date()),
|
|
CreditTransaction(type: .videoUnderstanding, date: Calendar.current.date(byAdding: .hour, value: -4, to: Date()) ?? Date()),
|
|
CreditTransaction(type: .mysteryBoxPurchase, date: Calendar.current.date(byAdding: .day, value: -1, to: Date()) ?? Date()),
|
|
CreditTransaction(type: .dailyBonus, date: Calendar.current.date(byAdding: .day, value: -1, to: Date()) ?? Date()),
|
|
CreditTransaction(type: .subscriptionBonus, date: Calendar.current.date(byAdding: .day, value: -2, to: Date()) ?? Date())
|
|
]
|
|
|
|
var body: some View {
|
|
NavigationView {
|
|
ScrollView {
|
|
VStack(spacing: 0) {
|
|
// 导航栏
|
|
navigationHeader
|
|
|
|
// 主积分卡片
|
|
mainCreditsCard
|
|
|
|
// 积分历史
|
|
creditsHistorySection
|
|
|
|
Spacer(minLength: 100)
|
|
}
|
|
}
|
|
.background(Color(.systemGroupedBackground))
|
|
.navigationBarHidden(true)
|
|
}
|
|
}
|
|
|
|
// MARK: - 导航栏
|
|
private var navigationHeader: some View {
|
|
NaviHeader(title: "Credits") {
|
|
presentationMode.wrappedValue.dismiss()
|
|
}
|
|
}
|
|
|
|
// MARK: - 主积分卡片
|
|
private var mainCreditsCard: some View {
|
|
VStack(spacing: 0) {
|
|
// 主要积分显示区域
|
|
HStack {
|
|
// 左侧三角形图标
|
|
Circle()
|
|
.fill(Color.black)
|
|
.frame(width: 80, height: 80)
|
|
.overlay(
|
|
Image(systemName: "triangle.fill")
|
|
.foregroundColor(.white)
|
|
.font(.system(size: 24, weight: .bold))
|
|
)
|
|
|
|
Spacer()
|
|
|
|
// 右侧积分信息
|
|
VStack(alignment: .trailing, spacing: 8) {
|
|
HStack(spacing: 8) {
|
|
Circle()
|
|
.fill(Color.black)
|
|
.frame(width: 24, height: 24)
|
|
.overlay(
|
|
Image(systemName: "triangle.fill")
|
|
.foregroundColor(.white)
|
|
.font(.system(size: 8))
|
|
)
|
|
|
|
Text("\(totalCredits)")
|
|
.font(Typography.font(for: .headline, family: .quicksandBold, size: 36))
|
|
.foregroundColor(.black)
|
|
}
|
|
|
|
Text("Expiring Today : \(expiringToday)")
|
|
.font(Typography.font(for: .body, family: .quicksand))
|
|
.foregroundColor(.black.opacity(0.8))
|
|
}
|
|
}
|
|
.padding(Theme.Spacing.xl)
|
|
|
|
// 虚线分隔
|
|
DashedLine()
|
|
.stroke(Color.black.opacity(0.3), style: StrokeStyle(lineWidth: 1, dash: [5, 5]))
|
|
.frame(height: 1)
|
|
.padding(.horizontal, Theme.Spacing.xl)
|
|
|
|
// 积分规则展开区域
|
|
creditsRulesSection
|
|
}
|
|
.background(
|
|
LinearGradient(
|
|
colors: [
|
|
Color(hex: "FFB645"),
|
|
Color(hex: "FFA726")
|
|
],
|
|
startPoint: .topLeading,
|
|
endPoint: .bottomTrailing
|
|
)
|
|
)
|
|
.cornerRadius(Theme.CornerRadius.large)
|
|
.padding(.horizontal, Theme.Spacing.xl)
|
|
.padding(.top, Theme.Spacing.xl)
|
|
}
|
|
|
|
// MARK: - 积分规则区域
|
|
private var creditsRulesSection: some View {
|
|
VStack(spacing: 0) {
|
|
// 规则标题按钮
|
|
Button(action: {
|
|
withAnimation(.easeInOut(duration: 0.3)) {
|
|
showRules.toggle()
|
|
}
|
|
}) {
|
|
HStack {
|
|
Text("Credits Rules")
|
|
.font(Typography.font(for: .body, family: .quicksandBold))
|
|
.foregroundColor(.black)
|
|
|
|
Spacer()
|
|
|
|
Image(systemName: showRules ? "chevron.up" : "chevron.down")
|
|
.foregroundColor(.black)
|
|
.font(.system(size: 14, weight: .medium))
|
|
}
|
|
.padding(.horizontal, Theme.Spacing.xl)
|
|
.padding(.vertical, Theme.Spacing.lg)
|
|
}
|
|
|
|
// 规则内容
|
|
if showRules {
|
|
VStack(alignment: .leading, spacing: Theme.Spacing.sm) {
|
|
Text("Credits can be used for material indexing (1 credit per photo or per second of video) and for buying blind boxes (100 credits each).")
|
|
.font(Typography.font(for: .subtitle, family: .quicksand))
|
|
.foregroundColor(.black.opacity(0.8))
|
|
.multilineTextAlignment(.leading)
|
|
}
|
|
.padding(.horizontal, Theme.Spacing.xl)
|
|
.padding(.bottom, Theme.Spacing.lg)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - 积分历史区域
|
|
private var creditsHistorySection: some View {
|
|
VStack(alignment: .leading, spacing: Theme.Spacing.lg) {
|
|
Text("Points History")
|
|
.font(Typography.font(for: .title, family: .quicksandBold))
|
|
.foregroundColor(Theme.Colors.textPrimary)
|
|
.padding(.horizontal, Theme.Spacing.xl)
|
|
|
|
LazyVStack(spacing: 0) {
|
|
ForEach(Array(transactions.enumerated()), id: \.element.id) { index, transaction in
|
|
CreditTransactionRow(
|
|
transaction: transaction,
|
|
isLast: index == transactions.count - 1
|
|
)
|
|
}
|
|
}
|
|
.background(Color(.systemBackground))
|
|
.cornerRadius(Theme.CornerRadius.medium)
|
|
.padding(.horizontal, Theme.Spacing.xl)
|
|
}
|
|
.padding(.top, Theme.Spacing.xl)
|
|
}
|
|
}
|
|
|
|
// MARK: - 积分交易行组件
|
|
struct CreditTransactionRow: View {
|
|
let transaction: CreditTransaction
|
|
let isLast: Bool
|
|
|
|
var body: some View {
|
|
VStack(spacing: 0) {
|
|
HStack(spacing: Theme.Spacing.lg) {
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text(transaction.type.rawValue)
|
|
.font(Typography.font(for: .body, family: .quicksandBold))
|
|
.foregroundColor(Theme.Colors.textPrimary)
|
|
|
|
Text(formatDate(transaction.date))
|
|
.font(Typography.font(for: .caption, family: .quicksand))
|
|
.foregroundColor(Theme.Colors.textSecondary)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
Text("\(transaction.creditChange > 0 ? "+" : "")\(transaction.creditChange)")
|
|
.font(Typography.font(for: .body, family: .quicksandBold))
|
|
.foregroundColor(transaction.creditChange > 0 ? Theme.Colors.success : Theme.Colors.textPrimary)
|
|
}
|
|
.padding(.horizontal, Theme.Spacing.lg)
|
|
.padding(.vertical, Theme.Spacing.lg)
|
|
|
|
if !isLast {
|
|
Divider()
|
|
.background(Theme.Colors.borderLight)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func formatDate(_ date: Date) -> String {
|
|
let formatter = DateFormatter()
|
|
formatter.dateFormat = "MM-dd-yyyy"
|
|
return formatter.string(from: date)
|
|
}
|
|
}
|
|
|
|
// MARK: - 虚线组件
|
|
struct DashedLine: Shape {
|
|
func path(in rect: CGRect) -> Path {
|
|
var path = Path()
|
|
path.move(to: CGPoint(x: 0, y: 0))
|
|
path.addLine(to: CGPoint(x: rect.width, y: 0))
|
|
return path
|
|
}
|
|
}
|
|
|
|
// MARK: - 预览
|
|
#Preview {
|
|
CreditsDetailView()
|
|
}
|