// // SubscriptionStatusBar.swift // wake // // Created by fairclip on 2025/8/19. // import SwiftUI // MARK: - 订阅状态枚举 enum SubscriptionStatus { case free case pioneer(expiryDate: Date) var title: String { switch self { case .free: return "" case .pioneer: return "Pioneer" } } var backgroundColor: Color { switch self { case .free: return .clear case .pioneer: return .clear } } var textColor: Color { switch self { case .free: return Theme.Colors.textPrimary case .pioneer: return .themeTextMessageMain } } var backgroundImageName: String { switch self { case .free: return "Free" case .pioneer: return "Pioneer" } } } // MARK: - 订阅状态栏组件 struct SubscriptionStatusBar: View { let status: SubscriptionStatus let onSubscribeTap: (() -> Void)? private let height: CGFloat init(status: SubscriptionStatus, height: CGFloat? = nil, onSubscribeTap: (() -> Void)? = nil) { self.status = status self.height = height ?? 155 // 默认高度为155 self.onSubscribeTap = onSubscribeTap } var body: some View { ZStack(alignment: .leading) { // SwiftUI 绘制的背景 SubscriptionBackground(status: status) .frame(maxWidth: .infinity, minHeight: 120) .clipped() // Main content container VStack(alignment: .leading, spacing: 0) { // Title - Centered vertically Text(status.title) .font(.system(size: 28, weight: .bold, design: .rounded)) .foregroundColor(status.textColor) .frame(maxHeight: .infinity, alignment: .center) // Center vertically .padding(.leading, 12) .padding(.top, height < 155 ? 30 : 40) // Expiry date - Bottom left if case .pioneer(let expiryDate) = status { VStack(alignment: .leading, spacing: 4) { Text("Expires on :") .font(.system(size: 12)) .foregroundColor(.themeTextMessageMain) Text(formatDate(expiryDate)) .font(.system(size: 12)) .fontWeight(.bold) .foregroundColor(.themeTextMessageMain) } .padding(.leading, 12) .padding(.bottom, 12) } } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) } .frame(height: height) } // MARK: - 日期格式化 private func formatDate(_ date: Date) -> String { let formatter = DateFormatter() formatter.dateFormat = "MMM d, yyyy" return formatter.string(from: date) } } // MARK: - 背景绘制 private struct SubscriptionBackground: View { let status: SubscriptionStatus var body: some View { ZStack(alignment: .topTrailing) { RoundedRectangle(cornerRadius: 20) .fill(background) .shadow(color: Color.black.opacity(0.06), radius: 10, x: 0, y: 6) // 装饰元素(右上角) if case .pioneer = status { Circle() .fill(Color.black.opacity(0.08)) .frame(width: 90, height: 90) .offset(x: 12, y: -12) } else { Circle() .fill(Color.black.opacity(0.04)) .frame(width: 70, height: 70) .offset(x: 12, y: -12) } } .clipShape(RoundedRectangle(cornerRadius: 20)) } private var background: some ShapeStyle { switch status { case .free: return LinearGradient(colors: [Color.white, Color.white.opacity(0.96)], startPoint: .topLeading, endPoint: .bottomTrailing) case .pioneer: return LinearGradient(colors: [Color.themePrimary.opacity(0.85), Color.orange.opacity(0.6)], startPoint: .topLeading, endPoint: .bottomTrailing) } } } // MARK: - 预览 #Preview { VStack(spacing: 20) { // Free status preview SubscriptionStatusBar( status: .free, onSubscribeTap: { print("Subscribe tapped") } ) .padding(.horizontal) // Pioneer status preview SubscriptionStatusBar( status: .pioneer( expiryDate: Calendar.current.date(byAdding: .month, value: 6, to: Date()) ?? Date() ) ) .padding(.horizontal) } .padding() .background(Theme.Colors.background) }