Newer Older
183 lines | 6.297kb
Bogdan Timofte authored 2 weeks ago
1
//
2
//  LiveView.swift
3
//  USB Meter
4
//
5
//  Created by Bogdan Timofte on 09/03/2020.
6
//  Copyright © 2020 Bogdan Timofte. All rights reserved.
7
//
8

            
9
import SwiftUI
10

            
11
struct LiveView: View {
12

            
13
    @EnvironmentObject private var meter: Meter
14

            
15
    var body: some View {
Bogdan Timofte authored 2 weeks ago
16
        VStack(alignment: .leading, spacing: 16) {
Bogdan Timofte authored 2 weeks ago
17
            HStack {
Bogdan Timofte authored 2 weeks ago
18
                Text("Live Data")
19
                    .font(.headline)
20
                Spacer()
21
                statusBadge
22
            }
23

            
24
            LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 12) {
25
                liveMetricCard(
26
                    title: "Voltage",
27
                    symbol: "bolt.fill",
28
                    color: .green,
29
                    value: "\(meter.voltage.format(decimalDigits: 3)) V",
30
                    range: rangeText(
31
                        min: meter.measurements.voltage.context.minValue,
32
                        max: meter.measurements.voltage.context.maxValue,
33
                        unit: "V"
34
                    )
35
                )
36

            
37
                liveMetricCard(
38
                    title: "Current",
39
                    symbol: "waveform.path.ecg",
40
                    color: .blue,
41
                    value: "\(meter.current.format(decimalDigits: 3)) A",
42
                    range: rangeText(
43
                        min: meter.measurements.current.context.minValue,
44
                        max: meter.measurements.current.context.maxValue,
45
                        unit: "A"
46
                    )
47
                )
48

            
49
                liveMetricCard(
50
                    title: "Power",
51
                    symbol: "flame.fill",
52
                    color: .pink,
53
                    value: "\(meter.power.format(decimalDigits: 3)) W",
54
                    range: rangeText(
55
                        min: meter.measurements.power.context.minValue,
56
                        max: meter.measurements.power.context.maxValue,
57
                        unit: "W"
58
                    )
59
                )
60

            
61
                liveMetricCard(
62
                    title: "Temperature",
63
                    symbol: "thermometer.medium",
64
                    color: .orange,
65
                    value: meter.primaryTemperatureDescription,
66
                    range: meter.secondaryTemperatureDescription
67
                )
68
            }
69

            
70
            if shouldShowSecondaryDetails {
71
                VStack(alignment: .leading, spacing: 12) {
72
                    Text("Details")
73
                        .font(.subheadline.weight(.semibold))
74
                        .foregroundColor(.secondary)
75

            
76
                    if meter.loadResistance > 0 {
77
                        secondaryDetailRow(
78
                            title: "Load",
79
                            value: "\(meter.loadResistance.format(decimalDigits: 1)) \u{2126}",
80
                            symbol: "cable.connector",
81
                            color: .yellow
82
                        )
Bogdan Timofte authored 2 weeks ago
83
                    }
Bogdan Timofte authored 2 weeks ago
84

            
85
                    if shouldShowChargerType {
86
                        secondaryDetailRow(
87
                            title: "Charger",
88
                            value: meter.chargerTypeDescription,
89
                            symbol: "bolt.badge.checkmark",
90
                            color: .purple
91
                        )
Bogdan Timofte authored 2 weeks ago
92
                    }
Bogdan Timofte authored 2 weeks ago
93
                }
Bogdan Timofte authored 2 weeks ago
94
                .padding(18)
95
                .meterCard(tint: meter.color, fillOpacity: 0.06, strokeOpacity: 0.10)
Bogdan Timofte authored 2 weeks ago
96
            }
97
        }
98
    }
Bogdan Timofte authored 2 weeks ago
99

            
100
    private var statusBadge: some View {
101
        Text(meter.operationalState == .dataIsAvailable ? "Live" : "Waiting")
102
            .font(.caption.weight(.semibold))
103
            .padding(.horizontal, 10)
104
            .padding(.vertical, 6)
105
            .foregroundColor(meter.operationalState == .dataIsAvailable ? .green : .secondary)
106
            .meterCard(
107
                tint: meter.operationalState == .dataIsAvailable ? .green : .secondary,
108
                fillOpacity: 0.12,
109
                strokeOpacity: 0.16,
110
                cornerRadius: 999
111
            )
112
    }
113

            
114
    private var shouldShowSecondaryDetails: Bool {
115
        meter.loadResistance > 0 || shouldShowChargerType
116
    }
117

            
118
    private var shouldShowChargerType: Bool {
119
        meter.supportsChargerDetection && meter.chargerTypeDescription != "Unknown"
120
    }
121

            
122
    private func liveMetricCard(
123
        title: String,
124
        symbol: String,
125
        color: Color,
126
        value: String,
127
        range: String?
128
    ) -> some View {
129
        VStack(alignment: .leading, spacing: 10) {
130
            HStack {
131
                Image(systemName: symbol)
132
                    .font(.system(size: 15, weight: .semibold))
133
                    .foregroundColor(color)
134
                    .frame(width: 34, height: 34)
135
                    .background(Circle().fill(color.opacity(0.12)))
136
                Spacer()
137
            }
138

            
139
            Text(title)
140
                .font(.subheadline.weight(.semibold))
141
                .foregroundColor(.secondary)
142

            
143
            Text(value)
144
                .font(.system(.title3, design: .rounded).weight(.bold))
145
                .monospacedDigit()
146

            
147
            if let range, !range.isEmpty {
148
                Text(range)
149
                    .font(.caption)
150
                    .foregroundColor(.secondary)
151
                    .lineLimit(2)
152
            }
153
        }
154
        .frame(maxWidth: .infinity, minHeight: 128, alignment: .leading)
155
        .padding(16)
156
        .meterCard(tint: color, fillOpacity: 0.10, strokeOpacity: 0.12)
157
    }
158

            
159
    private func secondaryDetailRow(
160
        title: String,
161
        value: String,
162
        symbol: String,
163
        color: Color
164
    ) -> some View {
165
        HStack(spacing: 12) {
166
            Image(systemName: symbol)
167
                .foregroundColor(color)
168
                .frame(width: 28)
169
            Text(title)
170
                .foregroundColor(.secondary)
171
            Spacer()
172
            Text(value)
173
                .fontWeight(.semibold)
174
                .multilineTextAlignment(.trailing)
175
        }
176
        .font(.footnote)
177
    }
178

            
179
    private func rangeText(min: Double, max: Double, unit: String) -> String? {
180
        guard min.isFinite, max.isFinite else { return nil }
181
        return "Min \(min.format(decimalDigits: 3)) \(unit)  Max \(max.format(decimalDigits: 3)) \(unit)"
182
    }
Bogdan Timofte authored 2 weeks ago
183
}