DFWatchFigmaTokens.swift 6.94 KB
import SwiftUI

enum DFWatchStressKind: String {
    case excellent
    case normal
    case little
    case overloaded

    var title: String {
        switch self {
        case .excellent:
            return "状态优秀"
        case .overloaded:
            return "压力过载"
        case .normal:
            return "状态正常"
        case .little:
            return "注意压力"
        }
    }

    var color: Color {
        switch self {
        case .excellent:
            return DFWatchFigmaColor.green
        case .overloaded:
            return DFWatchFigmaColor.red
        case .normal:
            return DFWatchFigmaColor.blue
        case .little:
            return DFWatchFigmaColor.orange
        }
    }
}

enum DFWatchFigmaColor {
    static let background = Color(hex: "0F0F11")
    static let purple = Color(hex: "845EEE")
    static let purpleText = Color(hex: "A285F4")
    static let green = Color(hex: "3BD49D")
    static let red = Color(hex: "FF5279")
    static let orange = Color(hex: "FF9A6E")
    static let blue = Color(hex: "7B9BFB")
    static let grayText = Color(hex: "B0B0B6")
    static let grayBar = Color(hex: "78787D")
    static let pro = Color(hex: "FFDF51")
}

struct DFWatchFigmaCanvas<Content: View>: View {
    private let designSize = CGSize(width: 352, height: 430)
    let content: (CGFloat) -> Content

    init(@ViewBuilder content: @escaping (CGFloat) -> Content) {
        self.content = content
    }

    var body: some View {
        GeometryReader { proxy in
            let scale = min(proxy.size.width / designSize.width, proxy.size.height / designSize.height)
            ZStack {
                DFWatchFigmaColor.background
                content(scale)
            }
            .frame(width: designSize.width * scale, height: designSize.height * scale)
            .position(x: proxy.size.width / 2, y: proxy.size.height / 2)
            .clipped()
        }
        .background(DFWatchFigmaColor.background)
        .ignoresSafeArea()
    }
}

extension CGFloat {
    func df(_ scale: CGFloat) -> CGFloat {
        self * scale
    }
}

extension Int {
    func df(_ scale: CGFloat) -> CGFloat {
        CGFloat(self) * scale
    }
}

extension Double {
    func df(_ scale: CGFloat) -> CGFloat {
        CGFloat(self) * scale
    }
}

struct DFWatchFigmaHeader: View {
    var subtitle: String?

    var body: some View {
        HStack {
            Circle()
                .fill(Color.white.opacity(0.12))
                .frame(width: 32, height: 32)
                .overlay(
                    Image(systemName: "gearshape.fill")
                        .font(.system(size: 14, weight: .medium))
                        .foregroundColor(.white)
                )
            Spacer()
            VStack(alignment: .trailing, spacing: 1) {
                Text(.now, style: .time)
                    .font(.system(size: 14, weight: .medium))
                    .foregroundColor(.white)
                if let subtitle {
                    Text(subtitle)
                        .font(.system(size: 12, weight: .regular))
                        .foregroundColor(.white.opacity(0.6))
                }
            }
        }
    }
}

struct DFWatchFigmaProBadge: View {
    var body: some View {
        HStack(spacing: 0) {
            Image(systemName: "crown.fill")
                .font(.system(size: 7, weight: .bold))
                .frame(width: 13, height: 13)
            Text("PRO")
                .font(.system(size: 9, weight: .medium))
        }
        .foregroundColor(DFWatchFigmaColor.background)
        .padding(.horizontal, 4)
        .background(DFWatchFigmaColor.pro)
        .clipShape(Capsule())
    }
}

struct DFWatchFigmaSegmentBar: View {
    let selected: DFWatchStressKind?
    
    let allKinds: [DFWatchStressKind] = [.overloaded, .little,  .normal, .excellent]
    let spacing: CGFloat = 2

    var body: some View {
        if let selected{
            GeometryReader { proxy in
                HStack(spacing: spacing) {
                    let extraWidth: CGFloat = proxy.size.width / 5
                    let availableWidth = proxy.size.width - CGFloat(allKinds.count - 1) * spacing - extraWidth
                    let itemWidth = floor(availableWidth/4)
                    ForEach(allKinds, id: \.rawValue){kind in
                        DFWatchKindBar(width: selected == kind ? (itemWidth + extraWidth) : itemWidth, kind: kind, selectedKind: selected)
                    }
                }
            }
        }else{
            Capsule()
                .fill(Color(hex: "#78787D"))
                .frame(width: .infinity, height: 8)
        }
    }
}

struct DFWatchKindBar: View {
    let width: CGFloat
    let kind: DFWatchStressKind
    let selectedKind: DFWatchStressKind
    
    var isSelected: Bool{
        kind == selectedKind
    }
    
    var body: some View {
        ZStack{
            Capsule()
                .fill(kind.color)
                .frame(width: width, height: 8)
            if isSelected{
                Capsule()
                    .stroke(kind.color, lineWidth: 4)
                    .frame(width: 14, height: 14)
                    .background(.black)
            }
        }
    }
}

struct DFWatchFigmaBottomMetrics: View {
    let scale: CGFloat
    var hasData: Bool
    var heartRate: String = "89"
    var steps: String = "2486"

    var body: some View {
        ZStack {
            DFMetricBubble(scale: scale, value: hasData ? heartRate : "0", icon: "heart.fill", tint: DFWatchFigmaColor.red)
                .position(x: 69.df(scale), y: 362.df(scale))
            DFMetricBubble(scale: scale, value: "", icon: "sparkles", tint: DFWatchFigmaColor.purple)
                .position(x: 176.df(scale), y: 362.df(scale))
            DFMetricBubble(scale: scale, value: hasData ? steps : "0", icon: "figure.walk", tint: DFWatchFigmaColor.green)
                .position(x: 287.df(scale), y: 362.df(scale))
        }
    }
}

private struct DFMetricBubble: View {
    let scale: CGFloat
    let value: String
    let icon: String
    let tint: Color

    var body: some View {
        ZStack {
            Circle()
                .fill(Color.white.opacity(0.10))
                .frame(width: 71.df(scale), height: 71.df(scale))
            Circle()
                .trim(from: 0, to: value.isEmpty ? 1 : 0.72)
                .stroke(tint, style: StrokeStyle(lineWidth: max(1, 5.df(scale)), lineCap: .round))
                .rotationEffect(.degrees(-90))
                .frame(width: 63.df(scale), height: 63.df(scale))
            VStack(spacing: 7.df(scale)) {
                Text(value)
                    .font(.system(size: 18.df(scale), weight: .medium))
                    .foregroundColor(.white)
                    .opacity(value.isEmpty ? 0 : 1)
                Image(systemName: icon)
                    .font(.system(size: 18.df(scale), weight: .semibold))
                    .foregroundColor(.white)
            }
        }
        .frame(width: 82.df(scale), height: 82.df(scale))
    }
}