wake-ios/wake/View/Subscribe/Components/SubscriptionStatusBar.swift

237 lines
7.6 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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