1 contributor
//
// LiveView.swift
// USB Meter
//
// Created by Bogdan Timofte on 09/03/2020.
// Copyright © 2020 Bogdan Timofte. All rights reserved.
//
import SwiftUI
struct LiveView: View {
@EnvironmentObject private var meter: Meter
var body: some View {
VStack(alignment: .leading, spacing: 16) {
HStack {
Text("Live Data")
.font(.headline)
Spacer()
statusBadge
}
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 12) {
liveMetricCard(
title: "Voltage",
symbol: "bolt.fill",
color: .green,
value: "\(meter.voltage.format(decimalDigits: 3)) V",
range: rangeText(
min: meter.measurements.voltage.context.minValue,
max: meter.measurements.voltage.context.maxValue,
unit: "V"
)
)
liveMetricCard(
title: "Current",
symbol: "waveform.path.ecg",
color: .blue,
value: "\(meter.current.format(decimalDigits: 3)) A",
range: rangeText(
min: meter.measurements.current.context.minValue,
max: meter.measurements.current.context.maxValue,
unit: "A"
)
)
liveMetricCard(
title: "Power",
symbol: "flame.fill",
color: .pink,
value: "\(meter.power.format(decimalDigits: 3)) W",
range: rangeText(
min: meter.measurements.power.context.minValue,
max: meter.measurements.power.context.maxValue,
unit: "W"
)
)
liveMetricCard(
title: "Temperature",
symbol: "thermometer.medium",
color: .orange,
value: meter.primaryTemperatureDescription,
range: meter.secondaryTemperatureDescription
)
}
if shouldShowSecondaryDetails {
VStack(alignment: .leading, spacing: 12) {
Text("Details")
.font(.subheadline.weight(.semibold))
.foregroundColor(.secondary)
if meter.loadResistance > 0 {
secondaryDetailRow(
title: "Load",
value: "\(meter.loadResistance.format(decimalDigits: 1)) \u{2126}",
symbol: "cable.connector",
color: .yellow
)
}
if shouldShowChargerType {
secondaryDetailRow(
title: "Charger",
value: meter.chargerTypeDescription,
symbol: "bolt.badge.checkmark",
color: .purple
)
}
}
.padding(18)
.meterCard(tint: meter.color, fillOpacity: 0.06, strokeOpacity: 0.10)
}
}
}
private var statusBadge: some View {
Text(meter.operationalState == .dataIsAvailable ? "Live" : "Waiting")
.font(.caption.weight(.semibold))
.padding(.horizontal, 10)
.padding(.vertical, 6)
.foregroundColor(meter.operationalState == .dataIsAvailable ? .green : .secondary)
.meterCard(
tint: meter.operationalState == .dataIsAvailable ? .green : .secondary,
fillOpacity: 0.12,
strokeOpacity: 0.16,
cornerRadius: 999
)
}
private var shouldShowSecondaryDetails: Bool {
meter.loadResistance > 0 || shouldShowChargerType
}
private var shouldShowChargerType: Bool {
meter.supportsChargerDetection && meter.chargerTypeDescription != "Unknown"
}
private func liveMetricCard(
title: String,
symbol: String,
color: Color,
value: String,
range: String?
) -> some View {
VStack(alignment: .leading, spacing: 10) {
HStack {
Image(systemName: symbol)
.font(.system(size: 15, weight: .semibold))
.foregroundColor(color)
.frame(width: 34, height: 34)
.background(Circle().fill(color.opacity(0.12)))
Spacer()
}
Text(title)
.font(.subheadline.weight(.semibold))
.foregroundColor(.secondary)
Text(value)
.font(.system(.title3, design: .rounded).weight(.bold))
.monospacedDigit()
if let range, !range.isEmpty {
Text(range)
.font(.caption)
.foregroundColor(.secondary)
.lineLimit(2)
}
}
.frame(maxWidth: .infinity, minHeight: 128, alignment: .leading)
.padding(16)
.meterCard(tint: color, fillOpacity: 0.10, strokeOpacity: 0.12)
}
private func secondaryDetailRow(
title: String,
value: String,
symbol: String,
color: Color
) -> some View {
HStack(spacing: 12) {
Image(systemName: symbol)
.foregroundColor(color)
.frame(width: 28)
Text(title)
.foregroundColor(.secondary)
Spacer()
Text(value)
.fontWeight(.semibold)
.multilineTextAlignment(.trailing)
}
.font(.footnote)
}
private func rangeText(min: Double, max: Double, unit: String) -> String? {
guard min.isFinite, max.isFinite else { return nil }
return "Min \(min.format(decimalDigits: 3)) \(unit) Max \(max.format(decimalDigits: 3)) \(unit)"
}
}