DFWatchHeartBeatCircle.swift 3.92 KB
//
//  DFWatchHeartBeatCircle.swift
//  iwatch
//
//  Created by 权海 on 2026/6/12.
//

import SwiftUI

struct DFWatchHeartBeatCircle: View {
    /// 0...1
    var progress: CGFloat

    /// 中间显示的分数
    var value: Int?

    var lineWidth: CGFloat = 6
    var markerSize: CGFloat = 8

    private let gradientColors: [Color] = [
        Color(hex: "#3BD49D"),
        Color(hex: "#7B9BFB"),
        Color(hex: "#FF9A6E"),
        Color(hex: "#FF5279")
    ]

    var body: some View {
        GeometryReader { geometry in
            let size = min(geometry.size.width, geometry.size.height)
            let arcSize = size - markerSize
            let startAngle: CGFloat = 90
            let sweepAngle: CGFloat = 270
            let rotation = 0.128 * 360

            let clampedProgress = min(max(progress, 0), 1)
            let markerAngle = (startAngle + rotation) + sweepAngle * clampedProgress

            ZStack {
                Color.black
                // 渐变进度圆弧
                Circle()
                    .trim(from: 0.25, to: 1)
                    .stroke(
                        AngularGradient(
                            gradient: Gradient(colors: gradientColors),
                            center: .center,
                            startAngle: .degrees(Double(startAngle)),
                            endAngle: .degrees(Double(startAngle + sweepAngle))
                        ),
                        style: StrokeStyle(
                            lineWidth: lineWidth,
                            lineCap: .round,
                            lineJoin: .round
                        )
                    )
                    .frame(width: arcSize, height: arcSize)
                    .rotationEffect(.degrees(rotation))
                    .opacity(value == nil ? 0.3 : 1)

                // 中间数值
                Text("\(value ?? 0)")
                    .font(.system(size: 9, weight: .bold, design: .rounded))
                    .lineLimit(1)
                    .foregroundStyle(.white)
                    .frame(maxWidth: geometry.size.width - 20)
                    .minimumScaleFactor(0.8)
                    .offset(y: -size * 0.02)

                // 底部爱心
                HeartBadgeView()
                    .frame(width: 10, height: 10)
                    .offset(y: geometry.size.height - 28)

                // 当前进度 marker
                if let _ = value{
                    let markerPoint = pointOnCircle(
                        angle: markerAngle,
                        radius: arcSize / 2,
                        center: CGPoint(x: size / 2, y: size / 2)
                    )
                    
                    Circle()
                        .fill(.clear)
                        .frame(width: markerSize, height: markerSize)
                        .overlay(
                            Circle()
                                .stroke(Color.black, lineWidth: 2)
                        )
                        .position(markerPoint)
                }
            }
            .frame(width: size, height: size)
        }
        .aspectRatio(1, contentMode: .fit)
    }

    private func pointOnCircle(
        angle: CGFloat,
        radius: CGFloat,
        center: CGPoint
    ) -> CGPoint {
        let radians = angle * .pi / 180
        return CGPoint(
            x: center.x + cos(radians) * radius,
            y: center.y + sin(radians) * radius
        )
    }
}

struct HeartBadgeView: View {
    var body: some View {
        ZStack {
            Image(systemName: "heart.fill")
                .resizable()
                .scaledToFit()
                .foregroundStyle(Color(hex: "#FF5279"))

            Image(systemName: "waveform.path.ecg")
                .font(.system(size: 8, weight: .bold))
                .minimumScaleFactor(0.9)
                .foregroundStyle(.black)
                .scaledToFit()
                .padding(.all, 2)
        }
    }
}