init
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
import SwiftUI
|
||||
|
||||
/// Kachel auf dem Dashboard: Hauptwert gross, darunter die wichtigsten
|
||||
/// Nebenwerte und der Verbindungszustand.
|
||||
struct DeviceCard: View {
|
||||
let device: ConfiguredDevice
|
||||
let snapshot: DeviceSnapshot?
|
||||
let linkState: DeviceLinkState
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
header
|
||||
|
||||
if let snapshot, let primary = snapshot.primaryMetric {
|
||||
HStack(alignment: .firstTextBaseline, spacing: 4) {
|
||||
Text(primary.formatted)
|
||||
.font(.system(size: 44, weight: .semibold, design: .rounded))
|
||||
.contentTransition(.numericText())
|
||||
Text(primary.unit)
|
||||
.font(.title3.weight(.medium))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.foregroundStyle(snapshot.isStale ? .secondary : .primary)
|
||||
|
||||
secondaryValues(for: snapshot)
|
||||
} else {
|
||||
Text(placeholderText)
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.vertical, 18)
|
||||
}
|
||||
|
||||
footer
|
||||
}
|
||||
.padding(16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(Color(.secondarySystemGroupedBackground), in: .rect(cornerRadius: 16))
|
||||
}
|
||||
|
||||
private var header: some View {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: device.role.symbol)
|
||||
.foregroundStyle(.tint)
|
||||
VStack(alignment: .leading, spacing: 1) {
|
||||
Text(device.name)
|
||||
.font(.headline)
|
||||
Text(device.role.title)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
Spacer()
|
||||
StatusDot(linkState: linkState, isStale: snapshot?.isStale ?? true)
|
||||
}
|
||||
}
|
||||
|
||||
private func secondaryValues(for snapshot: DeviceSnapshot) -> some View {
|
||||
let others = snapshot.metrics
|
||||
.filter { $0.id != snapshot.primaryMetric?.id && $0.value != nil }
|
||||
.prefix(3)
|
||||
return HStack(spacing: 16) {
|
||||
ForEach(Array(others)) { metric in
|
||||
VStack(alignment: .leading, spacing: 1) {
|
||||
Text(metric.formattedWithUnit)
|
||||
.font(.subheadline.weight(.medium))
|
||||
.monospacedDigit()
|
||||
Text(metric.label)
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var footer: some View {
|
||||
if let fault = snapshot?.fault {
|
||||
Label(fault, systemImage: "exclamationmark.triangle.fill")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.red)
|
||||
.lineLimit(2)
|
||||
} else if let state = snapshot?.state {
|
||||
// Bei "Aus" ist erst der Grund die eigentliche Information.
|
||||
let reason = snapshot?.offReasons.first
|
||||
Text(reason.map { "\(state) · \($0)" } ?? state)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(2)
|
||||
} else if case .failed(let message) = linkState {
|
||||
Text(message)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.orange)
|
||||
.lineLimit(2)
|
||||
}
|
||||
}
|
||||
|
||||
private var placeholderText: String {
|
||||
switch linkState {
|
||||
case .needsKey: return "Verschlüsselungsschlüssel fehlt – im Detail eintragen."
|
||||
case .failed(let message): return message
|
||||
default: return "Warte auf Daten…"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Kleiner Punkt, der Verbindungszustand und Aktualität zusammenfasst.
|
||||
struct StatusDot: View {
|
||||
let linkState: DeviceLinkState
|
||||
let isStale: Bool
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 5) {
|
||||
Circle()
|
||||
.fill(color)
|
||||
.frame(width: 8, height: 8)
|
||||
Text(label)
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
private var color: Color {
|
||||
switch linkState {
|
||||
case .live: return isStale ? .orange : .green
|
||||
case .needsKey: return .orange
|
||||
case .failed: return .red
|
||||
default: return .secondary
|
||||
}
|
||||
}
|
||||
|
||||
private var label: String {
|
||||
if case .live = linkState, isStale { return "Veraltet" }
|
||||
return linkState.label
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user