cmux/Sources/Update/UpdateBadge.swift
2026-01-28 01:49:02 -08:00

65 lines
2 KiB
Swift

import SwiftUI
/// A badge view that displays the current state of an update operation.
struct UpdateBadge: View {
@ObservedObject var model: UpdateViewModel
@State private var rotationAngle: Double = 0
var body: some View {
badgeContent
.accessibilityLabel(model.text)
}
@ViewBuilder
private var badgeContent: some View {
switch model.effectiveState {
case .downloading(let download):
if let expectedLength = download.expectedLength, expectedLength > 0 {
let progress = min(1, max(0, Double(download.progress) / Double(expectedLength)))
ProgressRingView(progress: progress)
} else {
Image(systemName: "arrow.down.circle")
}
case .extracting(let extracting):
ProgressRingView(progress: min(1, max(0, extracting.progress)))
case .checking:
if let iconName = model.iconName {
Image(systemName: iconName)
.rotationEffect(.degrees(rotationAngle))
.onAppear {
withAnimation(.linear(duration: 2.5).repeatForever(autoreverses: false)) {
rotationAngle = 360
}
}
.onDisappear {
rotationAngle = 0
}
}
default:
if let iconName = model.iconName {
Image(systemName: iconName)
}
}
}
}
fileprivate struct ProgressRingView: View {
let progress: Double
let lineWidth: CGFloat = 2
var body: some View {
ZStack {
Circle()
.stroke(Color.primary.opacity(0.2), lineWidth: lineWidth)
Circle()
.trim(from: 0, to: progress)
.stroke(Color.primary, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round))
.rotationEffect(.degrees(-90))
.animation(.easeInOut(duration: 0.2), value: progress)
}
}
}