USB-Meter / USB Meter / Views / Powerbanks / PowerbankDetailView.swift
Newer Older
162 lines | 6.604kb
Bogdan Timofte authored a month ago
1
//
2
//  PowerbankDetailView.swift
3
//  USB Meter
4
//
5

            
6
import SwiftUI
7

            
8
struct PowerbankDetailView: View {
9
    @EnvironmentObject private var appData: AppData
10
    @Environment(\.dismiss) private var dismiss
11

            
12
    let powerbankID: UUID
13

            
14
    @State private var isEditing = false
15
    @State private var pendingDeletion = false
16

            
17
    private var powerbank: PowerbankSummary? {
18
        appData.powerbankSummaries.first { $0.id == powerbankID }
19
    }
20

            
21
    var body: some View {
22
        Group {
23
            if let powerbank {
24
                content(for: powerbank)
25
            } else {
26
                Text("Powerbank not found.")
27
                    .foregroundColor(.secondary)
28
            }
29
        }
30
        .navigationTitle(powerbank?.name ?? "Powerbank")
31
        .navigationBarTitleDisplayMode(.inline)
32
        .toolbar {
33
            ToolbarItem(placement: .navigationBarTrailing) {
34
                Menu {
35
                    Button("Edit") { isEditing = true }
36
                    Button("Delete", role: .destructive) { pendingDeletion = true }
37
                } label: {
38
                    Image(systemName: "ellipsis.circle")
39
                }
40
                .disabled(powerbank == nil)
41
            }
42
        }
43
        .sheet(isPresented: $isEditing) {
44
            if let powerbank {
45
                PowerbankEditorSheetView(powerbank: powerbank)
46
                    .environmentObject(appData)
47
            }
48
        }
49
        .confirmationDialog(
50
            "Delete \(powerbank?.name ?? "powerbank")?",
51
            isPresented: $pendingDeletion,
52
            titleVisibility: .visible
53
        ) {
54
            Button("Delete", role: .destructive) {
55
                if let powerbank {
56
                    _ = appData.deletePowerbank(id: powerbank.id)
57
                    dismiss()
58
                }
59
            }
60
            Button("Cancel", role: .cancel) {}
61
        } message: {
62
            Text("This will permanently remove the powerbank and any sessions where it is the subject. Sessions where it was the source will keep their data, with the source link cleared.")
63
        }
64
    }
65

            
66
    @ViewBuilder
67
    private func content(for powerbank: PowerbankSummary) -> some View {
68
        Form {
69
            Section(header: Text("Identity")) {
70
                row("Name", value: powerbank.name)
71
                HStack {
72
                    Text("QR identifier")
73
                    Spacer()
74
                    Text(powerbank.qrIdentifier)
75
                        .font(.caption.monospaced())
76
                        .foregroundColor(.secondary)
77
                        .textSelection(.enabled)
78
                }
79
            }
80

            
81
            Section(header: Text("Battery")) {
82
                row("Reporting", value: powerbank.batteryLevelReporting.title)
83
                if powerbank.batteryLevelReporting == .bars {
84
                    row("Bars resolution", value: "\(powerbank.batteryBarsCount)")
85
                }
86
                if let estimatedCapacityWh = powerbank.estimatedBatteryCapacityWh {
87
                    row("Capacity (charged)", value: "\(estimatedCapacityWh.format(decimalDigits: 2)) Wh")
88
                }
89
                if let apparentCapacityWh = powerbank.apparentCapacityWh {
90
                    row("Apparent capacity (delivered)", value: "\(apparentCapacityWh.format(decimalDigits: 2)) Wh")
91
                }
92
                if let efficiency = powerbank.sourceEfficiencyFactor {
93
                    row("Efficiency", value: "\((efficiency * 100).format(decimalDigits: 1))%")
94
                }
95
            }
96

            
97
            Section(header: Text("Source statistics")) {
98
                if powerbank.sessionsAsSource.isEmpty {
99
                    Text("No discharge sessions recorded yet.")
100
                        .font(.caption)
101
                        .foregroundColor(.secondary)
102
                } else {
103
                    row("Sessions as source", value: "\(powerbank.sessionsAsSource.count)")
104
                    row("Total Wh delivered", value: "\(powerbank.totalDeliveredEnergyWh.format(decimalDigits: 2)) Wh")
105
                    if let maxPower = powerbank.sourceMaximumPowerWatts {
106
                        row("Max power", value: "\(maxPower.format(decimalDigits: 2)) W")
107
                    }
108
                    if powerbank.sourceVoltageMaxCurrents.isEmpty == false {
109
                        VStack(alignment: .leading, spacing: 4) {
110
                            Text("Voltage profile")
111
                                .font(.subheadline.weight(.semibold))
112
                            ForEach(powerbank.sourceVoltageMaxCurrents.keys.sorted(), id: \.self) { voltage in
113
                                let maxAmps = powerbank.sourceVoltageMaxCurrents[voltage] ?? 0
114
                                HStack {
115
                                    Text(String(format: "%.1f V", voltage))
116
                                        .font(.caption.monospaced())
117
                                    Spacer()
118
                                    Text("max \(maxAmps.format(decimalDigits: 2)) A")
119
                                        .font(.caption)
120
                                        .foregroundColor(.secondary)
121
                                }
122
                            }
123
                        }
124
                    } else if powerbank.sourceObservedVoltageSelections.isEmpty == false {
125
                        row(
126
                            "Observed voltages",
127
                            value: powerbank.sourceObservedVoltageSelections
128
                                .map { String(format: "%.1fV", $0) }
129
                                .joined(separator: ", ")
130
                        )
131
                    }
132
                }
133
            }
134

            
135
            Section(header: Text("Charging history")) {
136
                if powerbank.sessionsAsSubject.isEmpty {
137
                    Text("Not charged yet.")
138
                        .font(.caption)
139
                        .foregroundColor(.secondary)
140
                } else {
141
                    row("Charge sessions", value: "\(powerbank.sessionsAsSubject.count)")
142
                    row("Total Wh received", value: "\(powerbank.totalReceivedEnergyWh.format(decimalDigits: 2)) Wh")
143
                }
144
            }
145

            
146
            if let notes = powerbank.notes, !notes.isEmpty {
147
                Section(header: Text("Notes")) {
148
                    Text(notes)
149
                }
150
            }
151
        }
152
    }
153

            
154
    private func row(_ label: String, value: String) -> some View {
155
        HStack {
156
            Text(label)
157
            Spacer()
158
            Text(value)
159
                .foregroundColor(.secondary)
160
        }
161
    }
162
}