USB-Meter / USB Meter / Views / ChargedDevices / ChargedDeviceLibrarySheetView.swift
Newer Older
223 lines | 7.799kb
Bogdan Timofte authored a month ago
1
//
2
//  ChargedDeviceLibrarySheetView.swift
3
//  USB Meter
4
//
5
//  Created by Codex on 10/04/2026.
6
//
7

            
8
import SwiftUI
9

            
10
enum ChargedDeviceLibraryMode {
11
    case device
12
    case charger
13

            
14
    var title: String {
15
        switch self {
16
        case .device:
17
            return "Devices"
18
        case .charger:
19
            return "Chargers"
20
        }
21
    }
22

            
23
    var singularTitle: String {
24
        switch self {
25
        case .device:
26
            return "Device"
27
        case .charger:
28
            return "Charger"
29
        }
30
    }
31

            
32
    var suggestedClass: ChargedDeviceClass {
33
        switch self {
34
        case .device:
35
            return .iphone
36
        case .charger:
37
            return .charger
38
        }
39
    }
40
}
41

            
42
struct ChargedDeviceLibrarySheetView: View {
43
    @EnvironmentObject private var appData: AppData
44

            
45
    @Binding var visibility: Bool
46

            
47
    let meterMACAddress: String
48
    let meterTint: Color
49
    let mode: ChargedDeviceLibraryMode
50

            
51
    @State private var editorVisibility = false
52
    @State private var editingChargedDevice: ChargedDeviceSummary?
53

            
54
    var body: some View {
55
        NavigationView {
56
            List {
57
                if displayedChargedDevices.isEmpty {
58
                    VStack(alignment: .leading, spacing: 10) {
59
                        Text("No \(mode.title.lowercased()) yet.")
60
                            .font(.headline)
61
                        Text(emptyStateDescription)
62
                            .font(.footnote)
63
                            .foregroundColor(.secondary)
64
                    }
65
                    .padding(.vertical, 10)
66
                    .listRowBackground(Color.clear)
67
                } else {
68
                    ForEach(displayedChargedDevices) { chargedDevice in
69
                        Button {
70
                            select(chargedDevice)
71
                            visibility = false
72
                        } label: {
73
                            ChargedDeviceLibraryRowView(
74
                                chargedDevice: chargedDevice,
75
                                isSelected: chargedDevice.id == selectedDeviceID
76
                            )
77
                        }
78
                        .buttonStyle(.plain)
79
                        .swipeActions(edge: .trailing, allowsFullSwipe: false) {
80
                            Button {
81
                                editingChargedDevice = chargedDevice
82
                            } label: {
83
                                Label("Edit", systemImage: "pencil")
84
                            }
85
                            .tint(.blue)
86
                        }
87
                        .contextMenu {
88
                            Button {
89
                                editingChargedDevice = chargedDevice
90
                            } label: {
91
                                Label("Edit Device", systemImage: "pencil")
92
                            }
93
                        }
94
                    }
95
                }
96
            }
97
            .listStyle(InsetGroupedListStyle())
98
            .background(
99
                LinearGradient(
100
                    colors: [meterTint.opacity(0.14), Color.clear],
101
                    startPoint: .topLeading,
102
                    endPoint: .bottomTrailing
103
                )
104
                .ignoresSafeArea()
105
            )
106
            .navigationTitle(mode.title)
107
            .navigationBarTitleDisplayMode(.inline)
108
            .toolbar {
109
                ToolbarItem(placement: .cancellationAction) {
110
                    Button("Done") {
111
                        visibility = false
112
                    }
113
                }
114
                ToolbarItem(placement: .confirmationAction) {
115
                    Button("New") {
116
                        editorVisibility = true
117
                    }
118
                }
119
            }
120
        }
121
        .navigationViewStyle(StackNavigationViewStyle())
122
        .sheet(isPresented: $editorVisibility) {
123
            ChargedDeviceEditorSheetView(
124
                meterMACAddress: meterMACAddress,
125
                suggestedDeviceClass: mode.suggestedClass
126
            )
127
                .environmentObject(appData)
128
        }
129
        .sheet(item: $editingChargedDevice) { chargedDevice in
130
            ChargedDeviceEditorSheetView(
131
                meterMACAddress: nil,
132
                chargedDevice: chargedDevice,
133
                suggestedDeviceClass: mode.suggestedClass
134
            )
135
            .environmentObject(appData)
136
        }
137
    }
138

            
139
    private var displayedChargedDevices: [ChargedDeviceSummary] {
140
        switch mode {
141
        case .device:
142
            return appData.deviceSummaries
143
        case .charger:
144
            return appData.chargerSummaries
145
        }
146
    }
147

            
148
    private var selectedDeviceID: UUID? {
149
        switch mode {
150
        case .device:
151
            return appData.currentChargedDeviceSummary(for: meterMACAddress)?.id
152
        case .charger:
153
            return appData.currentChargerSummary(for: meterMACAddress)?.id
154
        }
155
    }
156

            
157
    private var emptyStateDescription: String {
158
        switch mode {
159
        case .device:
160
            return "Create one here, then select it before or during a charging session. The selected device becomes the default for this meter."
161
        case .charger:
162
            return "Create one here, then select it for wireless charging sessions. The selected charger becomes the default wireless source for this meter."
163
        }
164
    }
165

            
166
    private func select(_ chargedDevice: ChargedDeviceSummary) {
167
        switch mode {
168
        case .device:
169
            appData.assignChargedDevice(chargedDevice.id, to: meterMACAddress)
170
        case .charger:
171
            appData.assignCharger(chargedDevice.id, to: meterMACAddress)
172
        }
173
    }
174
}
175

            
176
private struct ChargedDeviceLibraryRowView: View {
177
    let chargedDevice: ChargedDeviceSummary
178
    let isSelected: Bool
179

            
180
    var body: some View {
181
        HStack(alignment: .top, spacing: 14) {
182
            ChargedDeviceQRCodeView(qrIdentifier: chargedDevice.qrIdentifier, side: 58)
183

            
184
            VStack(alignment: .leading, spacing: 6) {
185
                HStack {
186
                    Label(chargedDevice.name, systemImage: chargedDevice.deviceClass.symbolName)
187
                        .font(.headline)
188
                        .foregroundColor(.primary)
189
                    Spacer()
190
                    if isSelected {
191
                        Image(systemName: "checkmark.circle.fill")
192
                            .foregroundColor(.green)
193
                    }
194
                }
195

            
196
                Text(chargedDevice.deviceClass.title)
197
                    .font(.caption.weight(.semibold))
198
                    .foregroundColor(.secondary)
199

            
200
                Text(chargedDevice.supportedChargingModes.map(\.title).joined(separator: " + "))
201
                    .font(.caption2)
202
                    .foregroundColor(.secondary)
203

            
204
                if let capacity = chargedDevice.estimatedBatteryCapacityWh {
205
                    Text("Estimated capacity: \(capacity.format(decimalDigits: 2)) Wh")
206
                        .font(.caption)
207
                        .foregroundColor(.secondary)
208
                } else if chargedDevice.isCharger, let chargerMaximumPowerWatts = chargedDevice.chargerMaximumPowerWatts {
209
                    Text("Max power: \(chargerMaximumPowerWatts.format(decimalDigits: 2)) W")
210
                        .font(.caption)
211
                        .foregroundColor(.secondary)
212
                }
213

            
214
                if let minimumCurrent = chargedDevice.resolvedCompletionCurrentAmps(for: chargedDevice.preferredChargingTransportMode) {
215
                    Text("Completion current: \(minimumCurrent.format(decimalDigits: 2)) A")
216
                        .font(.caption2)
217
                        .foregroundColor(.secondary)
218
                }
219
            }
220
        }
221
        .padding(.vertical, 4)
222
    }
223
}