126 lines
3.5 KiB
Swift
126 lines
3.5 KiB
Swift
import SwiftUI
|
|
|
|
/// Simple load state for screen view models.
|
|
enum LoadPhase: Equatable {
|
|
case idle
|
|
case loading
|
|
case loaded
|
|
case failed(String)
|
|
}
|
|
|
|
// MARK: - State / severity badges
|
|
|
|
struct StateBadge: View {
|
|
let text: String
|
|
let color: Color
|
|
|
|
var body: some View {
|
|
Text(text)
|
|
.font(.caption2.weight(.semibold))
|
|
.padding(.horizontal, 8)
|
|
.padding(.vertical, 3)
|
|
.background(color.opacity(0.18))
|
|
.foregroundStyle(color)
|
|
.clipShape(Capsule())
|
|
}
|
|
}
|
|
|
|
extension ONULifecycleState {
|
|
var color: Color {
|
|
switch self {
|
|
case .registered: return .green
|
|
case .unspecified: return .mint // also "online" (dev guide Procedure 2)
|
|
case .unprovisioned: return .blue
|
|
case .deregistered: return .gray
|
|
case .dyingGasp, .disallowedError: return .red
|
|
case .disabled, .disallowedAdmin, .disallowedRegID: return .orange
|
|
case .unknown: return .secondary
|
|
}
|
|
}
|
|
var label: String { self == .unknown ? "Unknown" : rawValue }
|
|
}
|
|
|
|
extension DeviceState {
|
|
var color: Color {
|
|
switch self {
|
|
case .online: return .green
|
|
case .offline: return .gray
|
|
case .unattached: return .orange
|
|
case .preProvisioned: return .blue
|
|
case .unknown: return .secondary
|
|
}
|
|
}
|
|
var label: String { self == .unknown ? "Unknown" : rawValue }
|
|
}
|
|
|
|
extension AlarmSeverity {
|
|
var color: Color {
|
|
switch self {
|
|
case .emergency, .alert, .critical: return .red
|
|
case .error: return .orange
|
|
case .warning: return .yellow
|
|
case .notice, .info: return .blue
|
|
case .debug: return .gray
|
|
case .unknown: return .secondary
|
|
}
|
|
}
|
|
var label: String { self == .unknown ? "unknown" : rawValue }
|
|
}
|
|
|
|
struct SeverityBadge: View {
|
|
let severity: AlarmSeverity
|
|
var body: some View { StateBadge(text: severity.label, color: severity.color) }
|
|
}
|
|
|
|
// MARK: - Count tile (dashboard)
|
|
|
|
struct CountTile: View {
|
|
let title: String
|
|
let value: Int
|
|
var systemImage: String? = nil
|
|
var tint: Color = .accentColor
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
HStack(spacing: 6) {
|
|
if let systemImage { Image(systemName: systemImage).foregroundStyle(tint) }
|
|
Text(title).font(.caption).foregroundStyle(.secondary)
|
|
}
|
|
Text("\(value)").font(.system(.title, design: .rounded).weight(.semibold))
|
|
}
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.padding()
|
|
.background(Color(.secondarySystemBackground))
|
|
.clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous))
|
|
}
|
|
}
|
|
|
|
// MARK: - Loading / error
|
|
|
|
struct LoadingView: View {
|
|
var label: String = "Loading…"
|
|
var body: some View {
|
|
VStack(spacing: 12) {
|
|
ProgressView()
|
|
Text(label).font(.subheadline).foregroundStyle(.secondary)
|
|
}
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
}
|
|
}
|
|
|
|
struct ErrorStateView: View {
|
|
let message: String
|
|
var retry: (() -> Void)? = nil
|
|
|
|
var body: some View {
|
|
ContentUnavailableView {
|
|
Label("Something went wrong", systemImage: "exclamationmark.triangle")
|
|
} description: {
|
|
Text(message)
|
|
} actions: {
|
|
if let retry {
|
|
Button("Retry", action: retry).buttonStyle(.borderedProminent)
|
|
}
|
|
}
|
|
}
|
|
}
|