// // 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 "Free" 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)? let size: String private let height: CGFloat private let backgroundColor: Color? init(status: SubscriptionStatus, height: CGFloat? = nil, backgroundColor: Color? = nil, onSubscribeTap: (() -> Void)? = nil, size: String? = "md") { self.status = status self.height = height ?? 155 // 默认高度为155 self.backgroundColor = backgroundColor self.onSubscribeTap = onSubscribeTap self.size = size ?? "md" } var body: some View { ZStack(alignment: .leading) { // SwiftUI 绘制的背景 SubscriptionBackground(status: status, customBackground: backgroundColor, size: size) .frame(maxWidth: .infinity, minHeight: 120) .clipped() // Main content container VStack(alignment: .leading, spacing: 0) { // Title - Centered vertically Text(status.title) .font(.system(size: size == "sm" ? 24 : 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 let customBackground: Color? let size: String private let parallelogramHeight: CGFloat private let parallelogramWidth: CGFloat init(status: SubscriptionStatus, customBackground: Color? = nil, size: String? = "md") { self.status = status self.customBackground = customBackground self.size = size ?? "md" self.parallelogramHeight = size == "sm" ? 14 : 18 self.parallelogramWidth = size == "sm" ? 12 : 16 } var body: some View { ZStack { // 背景底板:自定义颜色优先,否则根据状态给出渐变 if let color = customBackground { RoundedRectangle(cornerRadius: 10) .fill(color) .shadow(color: Color.black.opacity(0.06), radius: 10, x: 0, y: 6) } else { RoundedRectangle(cornerRadius: 10) .fill(defaultBackground) .shadow(color: Color.black.opacity(0.06), radius: 10, x: 0, y: 6) } // 左上角斜条纹(装饰) VStack { HStack { ParallelogramRow( count: 6, itemSize: CGSize(width: parallelogramWidth, height: parallelogramHeight), shear: -0.35, cornerRadius: 2, color: .black.opacity(0.85), spacing: 1 ) Spacer() } Spacer() } .padding(.top, 12) .padding(.leading, 16) // 右下角斜条纹(装饰) VStack { Spacer() HStack { Spacer() ParallelogramRow( count: 3, itemSize: CGSize(width: parallelogramWidth, height: parallelogramHeight), shear: -0.35, cornerRadius: 2, color: .black.opacity(0.85), spacing: 1 ) } } .padding(.bottom, 14) .padding(.trailing, 16) // 右上角圆形徽标 + 三角指针(与示例类似) VStack { HStack { Spacer() ZStack { CircleView(diameter: size == "sm" ? 70 : 100, color: .black) TriangleView( width: size == "sm" ? 20 : 24, height: size == "sm" ? 20 : 24, direction: .right, color: Color(white: 0.9), rotation: .degrees(157) ) } .offset(x: size == "sm" ? 8 : 12, y: size == "sm" ? -8 : -12) } Spacer() } } .clipShape(RoundedRectangle(cornerRadius: 16)) } private var defaultBackground: LinearGradient { switch status { case .free: return LinearGradient(colors: [Color(hex: "FFF8DE")], 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, backgroundColor: Color(white: 0.98), onSubscribeTap: { print("Subscribe tapped") } ) .padding(.horizontal) // Pioneer status preview SubscriptionStatusBar( status: .pioneer( expiryDate: Calendar.current.date(byAdding: .month, value: 6, to: Date()) ?? Date() ), backgroundColor: Color.orange.opacity(0.9) ) .padding(.horizontal) } .padding() .background(Theme.Colors.background) }