169 lines
5.0 KiB
Swift
169 lines
5.0 KiB
Swift
//
|
||
// 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)
|
||
}
|