Compare commits

...

10 Commits

Author SHA1 Message Date
jinyaqiu
831c7afd71 Merge branch 'upload' of https://git.fairclip.cn/FairClip/wake-ios into upload 2025-08-19 20:36:09 +08:00
jinyaqiu
ef65019e46 feat: 登录接口联调 2025-08-19 20:36:00 +08:00
jinyaqiu
7aea7b1535 feat: 文件上传成功 2025-08-19 20:36:00 +08:00
jinyaqiu
84db43f3b5 feat: 上传进度 2025-08-19 20:36:00 +08:00
jinyaqiu
f40828484c feat: 素材上传成 2025-08-19 20:36:00 +08:00
jinyaqiu
c828ff786a feat: 确认上传 2025-08-19 20:36:00 +08:00
jinyaqiu
b6391f0734 feat: 图片上传互获取url 2025-08-19 20:36:00 +08:00
jinyaqiu
45a39fecb9 feat: 添加中文注释 2025-08-19 20:36:00 +08:00
7d40fe3203 feat: 主题色 2025-08-19 15:24:53 +08:00
f361d73bf7 feat: navi header 2025-08-19 14:49:52 +08:00
11 changed files with 1043 additions and 0 deletions

4
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"lldb.library": "/Library/Developer/CommandLineTools/Library/PrivateFrameworks/LLDB.framework/Versions/A/LLDB",
"lldb.launch.expressions": "native"
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>wake.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
</dict>
</plist>

BIN
wake/.DS_Store vendored

Binary file not shown.

View File

@ -0,0 +1,121 @@
//
// ReturnButton.swift
// wake
//
// Created by Junhui on 2025/8/19.
//
import SwiftUI
///
struct ReturnButton: View {
let action: () -> Void
var iconName: String = "chevron.left"
var iconSize: TypographyStyle = .title
var iconColor: Color = .primary
var body: some View {
Button(action: action) {
Image(systemName: iconName)
.font(Typography.font(for: iconSize))
.fontWeight(.medium)
.foregroundColor(iconColor)
}
.buttonStyle(PlainButtonStyle())
}
}
///
struct ReturnButtonWithText: View {
let action: () -> Void
let text: String
var iconName: String = "chevron.left"
var spacing: CGFloat = 4
var textStyle: TypographyStyle = .body
var iconColor: Color = .primary
var textColor: Color = .primary
var body: some View {
Button(action: action) {
HStack(spacing: spacing) {
Image(systemName: iconName)
.font(Typography.font(for: textStyle, family: .quicksandRegular))
.fontWeight(.medium)
.foregroundColor(iconColor)
Text(text)
.font(Typography.font(for: textStyle, family: .quicksandRegular))
.foregroundColor(textColor)
}
}
.buttonStyle(PlainButtonStyle())
}
}
///
struct CircularReturnButton: View {
let action: () -> Void
var iconName: String = "chevron.left"
var size: CGFloat = 40
var backgroundColor: Color = Color(.systemBackground)
var iconColor: Color = .primary
var shadowRadius: CGFloat = 4
var body: some View {
Button(action: action) {
Image(systemName: iconName)
.font(Typography.font(for: .body, family: .quicksandRegular))
.foregroundColor(iconColor)
.frame(width: size, height: size)
.background(backgroundColor)
.clipShape(Circle())
.shadow(color: Color.black.opacity(0.1), radius: shadowRadius, x: 0, y: 2)
}
.buttonStyle(PlainButtonStyle())
}
}
#Preview("基础返回按钮") {
VStack(spacing: 20) {
HStack {
ReturnButton {
print("返回")
}
Spacer()
}
.padding()
Spacer()
}
.background(Color(.systemGroupedBackground))
}
#Preview("带文字返回按钮") {
VStack(spacing: 20) {
HStack {
ReturnButtonWithText(action: {
print("返回")
}, text: "Back")
Spacer()
}
.padding()
Spacer()
}
.background(Color(.systemGroupedBackground))
}
#Preview("圆形返回按钮") {
VStack(spacing: 20) {
HStack {
CircularReturnButton {
print("返回")
}
Spacer()
}
.padding()
Spacer()
}
.background(Color(.systemGroupedBackground))
}

View File

@ -0,0 +1,163 @@
//
// NaviHeader.swift
// wake
//
// Created by Junhui on 2025/8/19.
//
import SwiftUI
///
struct NaviHeader: View {
let title: String
let onBackTap: () -> Void
var showBackButton: Bool = true
var titleStyle: TypographyStyle = .title
var backgroundColor: Color = Color.clear
var rightContent: AnyView? = nil
var body: some View {
ZStack {
//
Text(title)
.font(Typography.font(for: titleStyle, family: .quicksandBold))
.fontWeight(.bold)
.foregroundColor(.primary)
//
HStack {
//
if showBackButton {
ReturnButton(action: onBackTap)
} else {
Color.clear
.frame(width: 30)
}
Spacer()
//
if let rightContent = rightContent {
rightContent
} else {
Color.clear
.frame(width: 30)
}
}
}
.padding(.horizontal, 20)
.padding(.top, 10)
.padding(.bottom, 20)
.background(backgroundColor)
}
}
///
struct NaviHeaderWithAction: View {
let title: String
let onBackTap: () -> Void
let rightButtonTitle: String
let onRightButtonTap: () -> Void
var showBackButton: Bool = true
var titleStyle: TypographyStyle = .title
var rightButtonStyle: TypographyStyle = .body
var backgroundColor: Color = Color.clear
var body: some View {
ZStack {
//
Text(title)
.font(Typography.font(for: titleStyle, family: .quicksandBold))
.fontWeight(.bold)
.foregroundColor(.primary)
//
HStack {
//
if showBackButton {
ReturnButton(action: onBackTap)
} else {
Color.clear
.frame(width: 30)
}
Spacer()
//
Button(action: onRightButtonTap) {
Text(rightButtonTitle)
.font(Typography.font(for: rightButtonStyle, family: .quicksandBold))
.fontWeight(.semibold)
.foregroundColor(.blue)
}
}
}
.padding(.horizontal, 20)
.padding(.top, 10)
.padding(.bottom, 20)
.background(backgroundColor)
}
}
///
struct SimpleNaviHeader: View {
let title: String
let onBackTap: () -> Void
var body: some View {
ZStack {
//
Text(title)
.font(Typography.font(for: .title, family: .quicksandBold))
.fontWeight(.bold)
.multilineTextAlignment(.center)
//
HStack {
ReturnButton(action: onBackTap)
Spacer()
}
}
.padding(.horizontal, 20)
.padding(.vertical, 16)
}
}
#Preview("基础导航头") {
VStack(spacing: 0) {
NaviHeader(title: "Settings") {
print("返回")
}
.background(Color(.systemBackground))
Spacer()
}
.background(Color(.systemGroupedBackground))
}
#Preview("带右侧按钮导航头") {
VStack(spacing: 0) {
NaviHeaderWithAction(
title: "Profile",
onBackTap: { print("返回") },
rightButtonTitle: "Save",
onRightButtonTap: { print("保存") }
)
.background(Color(.systemBackground))
Spacer()
}
.background(Color(.systemGroupedBackground))
}
#Preview("简洁导航头") {
VStack(spacing: 0) {
SimpleNaviHeader(title: "About") {
print("返回")
}
.background(Color(.systemBackground))
Spacer()
}
.background(Color(.systemGroupedBackground))
}

View File

@ -0,0 +1,34 @@
//
// ColorExtensions.swift
// wake
//
// Created by fairclip on 2025/8/19.
//
import SwiftUI
// MARK: - Color Extension for Hex Colors
extension Color {
///
/// - Parameter hex: (: "FF5733", "FFF8DE")
init(hex: String) {
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int: UInt64 = 0
Scanner(string: hex).scanHexInt64(&int)
let a, r, g, b: UInt64
switch hex.count {
case 6: // RGB (24-bit)
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
default:
(a, r, g, b) = (1, 1, 1, 0)
}
self.init(
.sRGB,
red: Double(r) / 255,
green: Double(g) / 255,
blue: Double(b) / 255,
opacity: Double(a) / 255
)
}
}

177
wake/Theme.swift Normal file
View File

@ -0,0 +1,177 @@
//
// Theme.swift
// wake
//
// Created by fairclip on 2025/8/19.
//
import SwiftUI
// MARK: -
struct Theme {
// MARK: -
struct Colors {
// MARK: -
static let primary = Color(hex: "FFB645") //
static let primaryLight = Color(hex: "FFF8DE") //
static let primaryDark = Color(hex: "E6A03D") //
// MARK: -
static let secondary = Color(hex: "6C7B7F") //
static let accent = Color(hex: "FF6B6B") //
// MARK: -
static let background = Color(hex: "F8F9FA") //
static let surface = Color.white //
static let surfaceSecondary = Color(hex: "F5F5F5") //
// MARK: -
static let textPrimary = Color.black //
static let textSecondary = Color(hex: "6B7280") //
static let textTertiary = Color(hex: "9CA3AF") //
static let textInverse = Color.white //
// MARK: -
static let success = Color(hex: "10B981") //
static let warning = Color(hex: "F59E0B") //
static let error = Color(hex: "EF4444") //
static let info = Color(hex: "3B82F6") //
// MARK: -
static let border = Color(hex: "E5E7EB") //
static let borderLight = Color(hex: "F3F4F6") //
static let borderDark = Color(hex: "D1D5DB") //
// MARK: -
static let freeBackground = primaryLight // Free
static let pioneerBackground = primary // Pioneer
static let subscribeButton = primary //
}
// MARK: -
struct Gradients {
static let primaryGradient = LinearGradient(
colors: [Colors.primary, Colors.primaryDark],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
static let backgroundGradient = LinearGradient(
colors: [Colors.background, Colors.surface],
startPoint: .top,
endPoint: .bottom
)
static let accentGradient = LinearGradient(
colors: [Colors.accent, Color(hex: "FF8E8E")],
startPoint: .leading,
endPoint: .trailing
)
}
// MARK: -
struct Shadows {
static let small = Color.black.opacity(0.1)
static let medium = Color.black.opacity(0.15)
static let large = Color.black.opacity(0.2)
//
static let cardShadow = (color: small, radius: CGFloat(4), x: CGFloat(0), y: CGFloat(2))
static let buttonShadow = (color: medium, radius: CGFloat(6), x: CGFloat(0), y: CGFloat(3))
static let modalShadow = (color: large, radius: CGFloat(12), x: CGFloat(0), y: CGFloat(8))
}
// MARK: -
struct CornerRadius {
static let small: CGFloat = 8
static let medium: CGFloat = 12
static let large: CGFloat = 16
static let extraLarge: CGFloat = 20
static let round: CGFloat = 50
}
// MARK: -
struct Spacing {
static let xs: CGFloat = 4
static let sm: CGFloat = 8
static let md: CGFloat = 12
static let lg: CGFloat = 16
static let xl: CGFloat = 20
static let xxl: CGFloat = 24
static let xxxl: CGFloat = 32
}
}
// MARK: - 便
extension Color {
/// 访
static var themePrimary: Color { Theme.Colors.primary }
static var themePrimaryLight: Color { Theme.Colors.primaryLight }
static var themeSecondary: Color { Theme.Colors.secondary }
static var themeAccent: Color { Theme.Colors.accent }
static var themeBackground: Color { Theme.Colors.background }
static var themeSurface: Color { Theme.Colors.surface }
static var themeTextPrimary: Color { Theme.Colors.textPrimary }
static var themeTextSecondary: Color { Theme.Colors.textSecondary }
}
// MARK: -
#Preview("Theme Colors") {
ScrollView {
VStack(spacing: Theme.Spacing.lg) {
//
ColorPreviewSection(title: "品牌色", colors: [
("Primary", Theme.Colors.primary),
("Primary Light", Theme.Colors.primaryLight),
("Primary Dark", Theme.Colors.primaryDark)
])
//
ColorPreviewSection(title: "辅助色", colors: [
("Secondary", Theme.Colors.secondary),
("Accent", Theme.Colors.accent)
])
//
ColorPreviewSection(title: "状态色", colors: [
("Success", Theme.Colors.success),
("Warning", Theme.Colors.warning),
("Error", Theme.Colors.error),
("Info", Theme.Colors.info)
])
}
.padding()
}
.background(Theme.Colors.background)
}
// MARK: -
struct ColorPreviewSection: View {
let title: String
let colors: [(String, Color)]
var body: some View {
VStack(alignment: .leading, spacing: Theme.Spacing.sm) {
Text(title)
.font(.headline)
.foregroundColor(Theme.Colors.textPrimary)
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 3), spacing: Theme.Spacing.sm) {
ForEach(colors, id: \.0) { name, color in
VStack(spacing: Theme.Spacing.xs) {
Rectangle()
.fill(color)
.frame(height: 60)
.cornerRadius(Theme.CornerRadius.small)
Text(name)
.font(.caption)
.foregroundColor(Theme.Colors.textSecondary)
}
}
}
}
}
}

View File

@ -6,6 +6,7 @@ enum FontFamily: String, CaseIterable {
case sankeiCute = "SankeiCutePopanime" //
case quicksand = "Quicksand x" //
case quicksandBold = "Quicksand-Bold"
case quicksandRegular = "Quicksand-Regular"
// case
// : case anotherFont = "AnotherFontName"

View File

@ -0,0 +1,140 @@
//
// 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 hasExpiry: Bool {
switch self {
case .free:
return false
case .pioneer:
return true
}
}
var backgroundColor: Color {
switch self {
case .free:
return Theme.Colors.freeBackground //
case .pioneer:
return Theme.Colors.pioneerBackground //
}
}
var textColor: Color {
switch self {
case .free:
return Theme.Colors.textPrimary
case .pioneer:
return Theme.Colors.textPrimary
}
}
}
// MARK: -
struct SubscriptionStatusBar: View {
let status: SubscriptionStatus
let onSubscribeTap: (() -> Void)?
init(status: SubscriptionStatus, onSubscribeTap: (() -> Void)? = nil) {
self.status = status
self.onSubscribeTap = onSubscribeTap
}
var body: some View {
HStack(spacing: 16) {
VStack(alignment: .leading, spacing: 8) {
//
Text(status.title)
.font(.system(size: 28, weight: .bold, design: .rounded))
.foregroundColor(status.textColor)
//
if case .pioneer(let expiryDate) = status {
VStack(alignment: .leading, spacing: 4) {
Text("Expires on :")
.font(.system(size: 14, weight: .medium))
.foregroundColor(status.textColor.opacity(0.8))
Text(formatDate(expiryDate))
.font(.system(size: 16, weight: .semibold))
.foregroundColor(status.textColor)
}
} else {
Button(action: {
onSubscribeTap?()
}) {
Text("Subscribe")
.font(.system(size: 14, weight: .semibold))
.foregroundColor(Theme.Colors.textPrimary)
.padding(.horizontal, 20)
.padding(.vertical, 8)
.background(Theme.Colors.subscribeButton)
.cornerRadius(Theme.CornerRadius.large)
}
}
}
Spacer()
//
Circle()
.fill(Color.black)
.frame(width: 60, height: 60)
.overlay(
Image(systemName: "play.fill")
.foregroundColor(.white)
.font(.title2)
.offset(x: 2) //
)
}
.padding(20)
.background(status.backgroundColor)
.cornerRadius(20)
.shadow(color: Color.black.opacity(0.1), radius: 4, x: 0, y: 2)
}
// MARK: -
private func formatDate(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "MMMM d, yyyy"
return formatter.string(from: date)
}
}
// MARK: -
#Preview("Free Status") {
VStack(spacing: 20) {
SubscriptionStatusBar(
status: .free,
onSubscribeTap: {
print("Subscribe tapped")
}
)
.padding()
SubscriptionStatusBar(
status: .pioneer(expiryDate: Calendar.current.date(byAdding: .month, value: 6, to: Date()) ?? Date())
)
.padding()
}
.background(Color(.systemGroupedBackground))
}

View File

@ -0,0 +1,389 @@
//
// SubscribeView.swift
// wake
//
// Created by fairclip on 2025/8/19.
//
import SwiftUI
// MARK: -
enum SubscriptionPlan: String, CaseIterable {
case free = "Free"
case pioneer = "Pioneer"
var displayName: String {
return self.rawValue
}
var price: String {
switch self {
case .free:
return "Free"
case .pioneer:
return "1$/Mon"
}
}
var isPopular: Bool {
return self == .pioneer
}
}
// MARK: -
struct SubscriptionFeature {
let name: String
let freeValue: String
let proValue: String
}
struct SubscribeView: View {
@State private var selectedPlan: SubscriptionPlan = .free
@State private var isLoading = false
@Environment(\.presentationMode) var presentationMode
//
private let features = [
SubscriptionFeature(name: "Mystery Box Purchase:", freeValue: "3 /week", proValue: "Free"),
SubscriptionFeature(name: "Material Upload:", freeValue: "50 images and\n5 videos/day", proValue: "Unlimited"),
SubscriptionFeature(name: "Free Credits:", freeValue: "200 /day", proValue: "500 /day")
]
var body: some View {
NavigationView {
ScrollView {
VStack(spacing: 0) {
//
navigationHeader
//
currentSubscriptionCard
//
creditsSection
//
subscriptionPlansSection
//
specialOfferBanner
//
featureComparisonTable
//
subscribeButton
//
legalLinks
Spacer(minLength: 100)
}
}
.background(Color(.systemGroupedBackground))
.navigationBarHidden(true)
}
}
// MARK: -
private var navigationHeader: some View {
NaviHeader(title: "Subscription") {
presentationMode.wrappedValue.dismiss()
}
}
// MARK: -
private var currentSubscriptionCard: some View {
VStack(spacing: 0) {
HStack {
VStack(alignment: .leading, spacing: 8) {
Text("Free")
.font(Typography.font(for: .headline, family: .quicksand))
.fontWeight(.bold)
Button(action: {
//
}) {
Text("Subscribe")
.font(Typography.font(for: .subtitle, family: .quicksand))
.fontWeight(.medium)
.foregroundColor(.black)
.padding(.horizontal, 16)
.padding(.vertical, 8)
.background(Color.orange)
.cornerRadius(20)
}
}
Spacer()
//
Circle()
.fill(Color.black)
.frame(width: 60, height: 60)
.overlay(
Image(systemName: "play.fill")
.foregroundColor(.white)
.font(.title2)
)
}
.padding(20)
.background(Color.orange.opacity(0.2))
.cornerRadius(16)
.padding(.horizontal, 20)
}
}
// MARK: -
private var creditsSection: some View {
HStack {
Text("Credits: 3290")
.font(Typography.font(for: .body, family: .quicksand))
.fontWeight(.medium)
Button(action: {
//
}) {
Image(systemName: "info.circle")
.foregroundColor(.gray)
}
Spacer()
Image(systemName: "chevron.right")
.foregroundColor(.gray)
.font(.caption)
}
.padding(.horizontal, 20)
.padding(.vertical, 16)
.background(Color(.systemBackground))
.cornerRadius(12)
.padding(.horizontal, 20)
.padding(.top, 20)
}
// MARK: -
private var subscriptionPlansSection: some View {
HStack(spacing: 16) {
// Free
SubscriptionPlanCard(
plan: .free,
isSelected: selectedPlan == .free,
onTap: { selectedPlan = .free }
)
// Pioneer
SubscriptionPlanCard(
plan: .pioneer,
isSelected: selectedPlan == .pioneer,
onTap: { selectedPlan = .pioneer }
)
}
.padding(.horizontal, 20)
.padding(.top, 20)
}
// MARK: -
private var specialOfferBanner: some View {
Text("First 100 users get a special deal: just $1 for your first month!")
.font(Typography.font(for: .caption, family: .quicksand))
.multilineTextAlignment(.center)
.padding(.horizontal, 20)
.padding(.top, 12)
.foregroundColor(.secondary)
}
// MARK: -
private var featureComparisonTable: some View {
VStack(spacing: 0) {
//
HStack {
Spacer()
Text("Free")
.font(Typography.font(for: .subtitle, family: .quicksand))
.fontWeight(.medium)
.frame(maxWidth: .infinity)
.foregroundColor(.gray)
Text("Pro")
.font(Typography.font(for: .subtitle, family: .quicksand))
.fontWeight(.medium)
.frame(maxWidth: .infinity)
.foregroundColor(.gray)
}
.padding(.vertical, 16)
.background(Color(.systemGray6))
//
ForEach(Array(features.enumerated()), id: \.offset) { index, feature in
FeatureRow(feature: feature, isLast: index == features.count - 1)
}
}
.background(Color(.systemBackground))
.cornerRadius(12)
.padding(.horizontal, 20)
.padding(.top, 20)
.shadow(color: Color.black.opacity(0.05), radius: 2, x: 0, y: 1)
}
// MARK: -
private var subscribeButton: some View {
VStack(spacing: 12) {
Button(action: {
handleSubscribe()
}) {
if isLoading {
HStack {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .white))
.scaleEffect(0.8)
Text("Subscribe")
.font(Typography.font(for: .body, family: .quicksand))
.fontWeight(.semibold)
}
} else {
Text("Subscribe")
.font(Typography.font(for: .body, family: .quicksand))
.fontWeight(.semibold)
}
}
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding(.vertical, 16)
.background(Color.blue)
.cornerRadius(25)
.disabled(isLoading)
Text("Get 5,000 Permanent Credits")
.font(Typography.font(for: .caption, family: .quicksand))
.foregroundColor(.secondary)
}
.padding(.horizontal, 20)
.padding(.top, 30)
}
// MARK: -
private var legalLinks: some View {
HStack(spacing: 8) {
Button("Terms of Service") {
//
}
Text("|")
.foregroundColor(.secondary)
Button("Privacy Policy") {
//
}
Text("|")
.foregroundColor(.secondary)
Button("Restore Purchase") {
//
}
}
.font(Typography.font(for: .caption, family: .quicksand))
.foregroundColor(.secondary)
.padding(.top, 16)
}
// MARK: -
private func handleSubscribe() {
isLoading = true
//
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
isLoading = false
//
}
}
}
// MARK: -
struct SubscriptionPlanCard: View {
let plan: SubscriptionPlan
let isSelected: Bool
let onTap: () -> Void
var body: some View {
Button(action: onTap) {
VStack(spacing: 12) {
if plan.isPopular {
HStack {
Spacer()
Text("Popular")
.font(Typography.font(for: .caption, family: .quicksand))
.fontWeight(.medium)
.foregroundColor(.white)
.padding(.horizontal, 12)
.padding(.vertical, 4)
.background(Color.black)
.cornerRadius(12)
}
.padding(.top, -8)
}
Text(plan.displayName)
.font(Typography.font(for: .title, family: .quicksand))
.fontWeight(.bold)
.foregroundColor(plan == .pioneer ? .white : .gray)
Text(plan.price)
.font(Typography.font(for: .body, family: .quicksand))
.fontWeight(.medium)
.foregroundColor(plan == .pioneer ? .white : .gray)
Spacer()
}
.frame(maxWidth: .infinity, minHeight: 120)
.padding(16)
.background(plan == .pioneer ? Color.orange : Color.white)
.cornerRadius(16)
.overlay(
RoundedRectangle(cornerRadius: 16)
.stroke(isSelected ? Color.blue : (plan == .free ? Color.blue : Color.clear), lineWidth: 2)
)
.shadow(color: Color.black.opacity(0.1), radius: 4, x: 0, y: 2)
}
}
}
// MARK: -
struct FeatureRow: View {
let feature: SubscriptionFeature
let isLast: Bool
var body: some View {
HStack {
Text(feature.name)
.font(Typography.font(for: .subtitle, family: .quicksand))
.fontWeight(.medium)
.frame(maxWidth: .infinity, alignment: .leading)
.foregroundColor(.primary)
Text(feature.freeValue)
.font(Typography.font(for: .caption, family: .quicksand))
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity)
.foregroundColor(.gray)
Text(feature.proValue)
.font(Typography.font(for: .caption, family: .quicksand))
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity)
.foregroundColor(.gray)
}
.padding(.vertical, 12)
.padding(.horizontal, 16)
.background(Color(.systemBackground))
if !isLast {
Divider()
.padding(.leading, 16)
}
}
}
#Preview {
SubscribeView()
}