Files
boc/aamos-admin-upgrade/ios/AamosAdmin/Components/HealthRing.swift
T
Bernt 6de2455917 v1.2.0: Add Global Markets footer, translated to 9 languages
- Added GLOBAL_MARKETS_TITLE to all translation files
- Updated footer with 12 markets (4 active + 8 upcoming)
- Translated market section to: zh-cn, zh-tw, ja, ko, th, vi, id, ms, hi
- Built and deployed to production
- CloudFront invalidation: I3RTMXVFDJXWLG3SYX208OP1CC
2026-07-08 19:56:03 +00:00

129 lines
4.5 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.
import SwiftUI
struct HealthRing: View {
let progress: Double // 0.0 1.0
let color: Color
var lineWidth: CGFloat = 12
var size: CGFloat = 120
var label: String? = nil
@State private var animatedProgress: Double = 0
private var clampedProgress: Double { min(max(progress, 0), 1) }
private var percentText: String { "\(Int((animatedProgress * 100).rounded()))" }
var body: some View {
ZStack {
Circle()
.stroke(color.opacity(0.15), lineWidth: lineWidth)
Circle()
.trim(from: 0, to: animatedProgress)
.stroke(
color,
style: StrokeStyle(lineWidth: lineWidth, lineCap: .round)
)
.rotationEffect(.degrees(-90))
.animation(
.spring(response: 0.85, dampingFraction: 0.72),
value: animatedProgress
)
VStack(spacing: 2) {
HStack(alignment: .firstTextBaseline, spacing: 1) {
Text(percentText)
.font(.system(size: size * 0.22, weight: .bold, design: .rounded))
.foregroundStyle(.primary)
.contentTransition(.numericText(countsDown: animatedProgress < clampedProgress))
.animation(
.spring(response: 0.85, dampingFraction: 0.72),
value: animatedProgress
)
Text("%")
.font(.system(size: size * 0.13, weight: .semibold, design: .rounded))
.foregroundStyle(.secondary)
}
if let label {
Text(label)
.font(.system(size: size * 0.1, weight: .medium))
.foregroundStyle(.secondary)
.lineLimit(1)
.minimumScaleFactor(0.5)
.frame(maxWidth: size * 0.55)
}
}
}
.frame(width: size, height: size)
.onAppear {
animatedProgress = clampedProgress
}
.onChange(of: progress) { _, newValue in
animatedProgress = min(max(newValue, 0), 1)
}
}
}
#Preview {
@Previewable @State var cpu: Double = 0.72
@Previewable @State var mem: Double = 0.45
@Previewable @State var disk: Double = 0.88
NavigationStack {
ScrollView {
LazyVStack(spacing: 32) {
HStack(spacing: 24) {
VStack(spacing: 8) {
HealthRing(
progress: cpu,
color: Color(.systemBlue),
label: "CPU"
)
Text("CPU")
.font(.caption.weight(.medium))
.foregroundStyle(.secondary)
}
VStack(spacing: 8) {
HealthRing(
progress: mem,
color: Color(.systemGreen),
label: "RAM"
)
Text("Memory")
.font(.caption.weight(.medium))
.foregroundStyle(.secondary)
}
VStack(spacing: 8) {
HealthRing(
progress: disk,
color: Color(.systemOrange),
label: "Disk"
)
Text("Disk")
.font(.caption.weight(.medium))
.foregroundStyle(.secondary)
}
}
HealthRing(
progress: 0.23,
color: Color(.systemRed),
lineWidth: 18,
size: 180,
label: "Error rate"
)
VStack(spacing: 8) {
Slider(value: $cpu, in: 0...1)
Text("CPU: \(Int(cpu * 100))%")
.font(.caption)
.foregroundStyle(.secondary)
}
.padding(.horizontal)
}
.padding()
}
.navigationTitle("Health")
.background(Color(.systemBackground))
}
}