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

            
8
import SwiftUI
9

            
10
struct ChargedDeviceEditorSheetView: View {
11
    @EnvironmentObject private var appData: AppData
12
    @Environment(\.dismiss) private var dismiss
13

            
14
    let meterMACAddress: String?
15
    let chargedDevice: ChargedDeviceSummary?
Bogdan Timofte authored a month ago
16
    let kind: ChargedDeviceKind
Bogdan Timofte authored a month ago
17

            
18
    @State private var name: String
19
    @State private var deviceClass: ChargedDeviceClass
Bogdan Timofte authored a month ago
20
    @State private var selectedTemplateID: String?
21
    @State private var lastAppliedTemplateID: String?
Bogdan Timofte authored a month ago
22
    @State private var chargingStateAvailability: ChargingStateAvailability
Bogdan Timofte authored a month ago
23
    @State private var supportsWiredCharging: Bool
24
    @State private var supportsWirelessCharging: Bool
25
    @State private var wirelessChargingProfile: WirelessChargingProfile
Bogdan Timofte authored a month ago
26
    @State private var completionCurrentTexts: [ChargeSessionKind: String]
Bogdan Timofte authored a month ago
27
    @State private var notes: String
28

            
29
    init(
30
        meterMACAddress: String?,
Bogdan Timofte authored a month ago
31
        kind: ChargedDeviceKind,
32
        chargedDevice: ChargedDeviceSummary? = nil
Bogdan Timofte authored a month ago
33
    ) {
34
        self.meterMACAddress = meterMACAddress
35
        self.chargedDevice = chargedDevice
Bogdan Timofte authored a month ago
36

            
Bogdan Timofte authored a month ago
37
        let resolvedKind = chargedDevice?.kind ?? kind
38
        self.kind = resolvedKind
39

            
40
        let initialDeviceClass = chargedDevice?.deviceClass ?? (resolvedKind == .charger ? .charger : .iphone)
Bogdan Timofte authored a month ago
41
        let initialChargingStateAvailability = initialDeviceClass.normalizedChargingStateAvailability(
42
            chargedDevice?.chargingStateAvailability ?? Self.suggestedChargingStateAvailability(for: initialDeviceClass)
43
        )
44
        let initialChargingSupport = initialDeviceClass.normalizedChargingSupport(
45
            supportsWiredCharging: chargedDevice?.supportsWiredCharging ?? true,
46
            supportsWirelessCharging: chargedDevice?.supportsWirelessCharging ?? true
47
        )
48
        let initialTemplateID = chargedDevice?.deviceTemplateID
Bogdan Timofte authored a month ago
49
        _name = State(initialValue: chargedDevice?.name ?? "")
50
        _deviceClass = State(initialValue: initialDeviceClass)
Bogdan Timofte authored a month ago
51
        _selectedTemplateID = State(initialValue: initialTemplateID)
52
        _lastAppliedTemplateID = State(initialValue: initialTemplateID)
53
        _chargingStateAvailability = State(initialValue: initialChargingStateAvailability)
54
        _supportsWiredCharging = State(initialValue: initialChargingSupport.wired)
55
        _supportsWirelessCharging = State(initialValue: initialChargingSupport.wireless)
Bogdan Timofte authored a month ago
56
        _wirelessChargingProfile = State(initialValue: chargedDevice?.wirelessChargingProfile ?? .genericQi)
Bogdan Timofte authored a month ago
57
        _completionCurrentTexts = State(initialValue: Self.makeCompletionCurrentTexts(for: chargedDevice))
Bogdan Timofte authored a month ago
58
        _notes = State(initialValue: chargedDevice?.notes ?? "")
59
    }
60

            
61
    var body: some View {
62
        NavigationView {
63
            Form {
Bogdan Timofte authored a month ago
64
                identitySection
Bogdan Timofte authored a month ago
65
                templateSection
Bogdan Timofte authored a month ago
66

            
67
                if kind == .device {
68
                    deviceChargeBehaviourSection
69
                    deviceChargingSupportSection
70
                    deviceCompletionSection
71
                } else {
72
                    chargerInformationSection
Bogdan Timofte authored a month ago
73
                }
74

            
Bogdan Timofte authored a month ago
75
                notesSection
Bogdan Timofte authored a month ago
76
            }
77
            .navigationTitle(editorTitle)
78
            .navigationBarTitleDisplayMode(.inline)
79
            .toolbar {
80
                ToolbarItem(placement: .cancellationAction) {
81
                    Button("Cancel") {
82
                        dismiss()
83
                    }
84
                }
85
                ToolbarItem(placement: .confirmationAction) {
86
                    Button(saveButtonTitle) {
Bogdan Timofte authored a month ago
87
                        save()
Bogdan Timofte authored a month ago
88
                    }
Bogdan Timofte authored a month ago
89
                    .disabled(!canSave)
Bogdan Timofte authored a month ago
90
                }
91
            }
92
        }
93
        .navigationViewStyle(StackNavigationViewStyle())
94
        .onChange(of: deviceClass) { newValue in
Bogdan Timofte authored a month ago
95
            guard kind == .device else {
96
                return
97
            }
Bogdan Timofte authored a month ago
98
            applyDeviceClassRules(for: newValue)
99
        }
100
        .onChange(of: selectedTemplateID) { newValue in
101
            applyTemplateSelection(
102
                previousTemplateID: lastAppliedTemplateID,
103
                newTemplateID: newValue
104
            )
105
            lastAppliedTemplateID = newValue
Bogdan Timofte authored a month ago
106
        }
107
        .onAppear {
Bogdan Timofte authored a month ago
108
            guard kind == .device else {
Bogdan Timofte authored a month ago
109
                return
110
            }
Bogdan Timofte authored a month ago
111
            applyDeviceClassRules(for: deviceClass)
Bogdan Timofte authored a month ago
112
        }
113
    }
114

            
Bogdan Timofte authored a month ago
115
    private var identitySection: some View {
116
        Section(header: Text("Identity")) {
117
            TextField(kind == .charger ? "Charger name" : "Name", text: $name)
118

            
119
            if kind == .device {
120
                Picker("Class", selection: $deviceClass) {
121
                    ForEach(ChargedDeviceClass.deviceCases) { deviceClass in
122
                        Label(deviceClass.title, systemImage: deviceClass.symbolName)
123
                            .tag(deviceClass)
124
                    }
125
                }
126
            }
127

            
128
            if let chargedDevice {
129
                Text(chargedDevice.qrIdentifier)
130
                    .font(.caption.monospaced())
131
                    .foregroundColor(.secondary)
132
                    .textSelection(.enabled)
133
            }
134
        }
135
    }
136

            
Bogdan Timofte authored a month ago
137
    private var templateSection: some View {
138
        Section(
139
            header: ContextInfoHeader(
140
                title: "Template",
141
                message: "Templates load from a JSON catalog and provide the starting icon and charging profile for common devices and chargers."
142
            )
143
        ) {
144
            Picker("Template", selection: $selectedTemplateID) {
145
                Text("Custom")
146
                    .tag(String?.none)
147

            
148
                ForEach(groupedTemplates, id: \.group) { group in
149
                    Section(group.group) {
150
                        ForEach(group.templates) { template in
151
                            Text(template.name)
152
                                .tag(template.id as String?)
153
                        }
154
                    }
155
                }
156
            }
157

            
158
            if let selectedTemplate {
159
                ChargedDeviceTemplateLabelView(
160
                    template: selectedTemplate,
161
                    iconPointSize: 18
162
                )
163
                .font(.subheadline.weight(.semibold))
164

            
165
                Text(selectedTemplate.capabilitySummary)
166
                    .font(.caption)
167
                    .foregroundColor(.secondary)
168
            } else {
169
                Text("Choose a template when you want a predefined icon and a starting charging setup.")
170
                    .font(.caption)
171
                    .foregroundColor(.secondary)
172
            }
173
        }
174
    }
175

            
Bogdan Timofte authored a month ago
176
    private var deviceChargeBehaviourSection: some View {
177
        Section(
178
            header: ContextInfoHeader(
179
                title: "Charge Behaviour",
180
                message: "Choose whether sessions for this device are recorded only while it is on, only while it is off, or explicitly in either state."
181
            )
182
        ) {
Bogdan Timofte authored a month ago
183
            if let enforcedChargingStateAvailability = deviceClass.enforcedChargingStateAvailability {
184
                VStack(alignment: .leading, spacing: 6) {
185
                    Label(enforcedChargingStateAvailability.title, systemImage: "lock.fill")
186
                        .font(.subheadline.weight(.semibold))
187
                    Text(enforcedChargingStateAvailability.description)
188
                        .font(.caption)
189
                        .foregroundColor(.secondary)
190
                }
191
            } else {
192
                Picker("Session Modes", selection: $chargingStateAvailability) {
193
                    ForEach(ChargingStateAvailability.allCases) { availability in
194
                        Text(availability.title)
195
                            .tag(availability)
196
                    }
Bogdan Timofte authored a month ago
197
                }
198
            }
199
        }
200
    }
201

            
202
    private var deviceChargingSupportSection: some View {
203
        Section(
204
            header: ContextInfoHeader(
205
                title: "Charging Support",
206
                message: "Enable the charging methods this device actually supports. Wireless devices can also keep a dedicated profile so learning stays separate."
207
            )
208
        ) {
Bogdan Timofte authored a month ago
209
            if let enforcedChargingSupport = deviceClass.enforcedChargingSupport {
210
                VStack(alignment: .leading, spacing: 6) {
211
                    Label(
212
                        Self.chargingSupportDescription(
213
                            supportsWiredCharging: enforcedChargingSupport.wired,
214
                            supportsWirelessCharging: enforcedChargingSupport.wireless
215
                        ),
216
                        systemImage: "lock.fill"
217
                    )
218
                    .font(.subheadline.weight(.semibold))
219

            
220
                    Text("This device class is fixed so sessions cannot be recorded with an impossible charging transport.")
221
                        .font(.caption)
222
                        .foregroundColor(.secondary)
223
                }
224
            } else {
225
                Toggle("Supports wired charging", isOn: $supportsWiredCharging)
226
                Toggle("Supports wireless charging", isOn: $supportsWirelessCharging)
227
            }
Bogdan Timofte authored a month ago
228

            
Bogdan Timofte authored a month ago
229
            if showsWirelessProfilePicker {
Bogdan Timofte authored a month ago
230
                Picker("Wireless profile", selection: $wirelessChargingProfile) {
231
                    ForEach(WirelessChargingProfile.allCases) { profile in
232
                        Text(profile.title)
233
                            .tag(profile)
234
                    }
235
                }
236

            
237
            }
238

            
239
            if supportedChargingModes.isEmpty {
240
                Text("Enable at least one charging method.")
241
                    .font(.footnote)
242
                    .foregroundColor(.secondary)
243
            }
244
        }
245
    }
246

            
247
    private var deviceCompletionSection: some View {
248
        Section(
249
            header: ContextInfoHeader(
250
                title: "Charge Completion",
251
                message: "Completion currents can be set per session type. Leave a value empty to keep learning that threshold automatically from sessions of the same type."
252
            )
253
        ) {
254
            if applicableSessionKinds.isEmpty {
255
                Text("Enable at least one charging method to configure stop currents.")
256
                    .font(.footnote)
257
                    .foregroundColor(.secondary)
258
            } else {
259
                ForEach(applicableSessionKinds) { sessionKind in
260
                    VStack(alignment: .leading, spacing: 6) {
261
                        TextField(
Bogdan Timofte authored a month ago
262
                            completionCurrentFieldLabel(for: sessionKind),
Bogdan Timofte authored a month ago
263
                            text: completionCurrentTextBinding(for: sessionKind)
264
                        )
265
                        .keyboardType(.decimalPad)
266
                    }
267
                    .padding(.vertical, 2)
268
                }
269
            }
270
        }
271
    }
272

            
273
    private var chargerInformationSection: some View {
274
        Section(
275
            header: ContextInfoHeader(
276
                title: "Charger",
277
                message: "Chargers are edited separately from devices. Their charge-session metrics are learned automatically from wireless sessions."
278
            )
279
        ) {
280
            EmptyView()
281
        }
282
    }
283

            
284
    private var notesSection: some View {
285
        Section(header: Text("Notes")) {
286
            TextField("Optional notes", text: $notes)
287
        }
288
    }
289

            
Bogdan Timofte authored a month ago
290
    private var editorTitle: String {
291
        if chargedDevice == nil {
Bogdan Timofte authored a month ago
292
            return "New \(kind.title)"
Bogdan Timofte authored a month ago
293
        }
Bogdan Timofte authored a month ago
294
        return "Edit \(kind.title)"
Bogdan Timofte authored a month ago
295
    }
296

            
297
    private var saveButtonTitle: String {
298
        chargedDevice == nil ? "Save" : "Update"
299
    }
300

            
Bogdan Timofte authored a month ago
301
    private var canSave: Bool {
302
        let hasValidName = name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false
303
        guard kind == .device else {
304
            return hasValidName
305
        }
306
        return hasValidName
307
            && (supportsWiredCharging || supportsWirelessCharging)
308
            && !hasInvalidCompletionCurrentEntry
309
    }
310

            
Bogdan Timofte authored a month ago
311
    private var availableTemplates: [ChargedDeviceTemplateDefinition] {
312
        ChargedDeviceTemplateCatalog.shared.templates(for: kind)
313
    }
314

            
315
    private var groupedTemplates: [(group: String, templates: [ChargedDeviceTemplateDefinition])] {
316
        Dictionary(grouping: availableTemplates, by: \.group)
317
            .keys
318
            .sorted()
319
            .map { group in
320
                (
321
                    group: group,
322
                    templates: availableTemplates.filter { $0.group == group }
323
                )
324
            }
325
    }
326

            
327
    private var selectedTemplate: ChargedDeviceTemplateDefinition? {
328
        ChargedDeviceTemplateCatalog.shared.template(id: selectedTemplateID)
329
    }
330

            
Bogdan Timofte authored a month ago
331
    private var supportedChargingModes: [ChargingTransportMode] {
332
        var modes: [ChargingTransportMode] = []
333
        if supportsWiredCharging {
334
            modes.append(.wired)
335
        }
336
        if supportsWirelessCharging {
337
            modes.append(.wireless)
338
        }
339
        return modes
340
    }
341

            
Bogdan Timofte authored a month ago
342
    private var applicableSessionKinds: [ChargeSessionKind] {
343
        supportedChargingModes.flatMap { chargingTransportMode in
344
            chargingStateAvailability.supportedModes.map { chargingStateMode in
345
                ChargeSessionKind(
346
                    chargingTransportMode: chargingTransportMode,
347
                    chargingStateMode: chargingStateMode
348
                )
349
            }
350
        }
351
    }
352

            
Bogdan Timofte authored a month ago
353
    private var showsWirelessProfilePicker: Bool {
354
        supportsWirelessCharging
355
            && deviceClass != .watch
356
            && supportedChargingModes.count > 1
357
    }
358

            
Bogdan Timofte authored a month ago
359
    private var parsedCompletionCurrents: [ChargeSessionKind: Double] {
360
        applicableSessionKinds.reduce(into: [ChargeSessionKind: Double]()) { result, sessionKind in
361
            guard let value = parsedOptionalCurrent(completionCurrentTexts[sessionKind] ?? "") else {
362
                return
363
            }
364
            result[sessionKind] = value
365
        }
366
    }
367

            
368
    private var hasInvalidCompletionCurrentEntry: Bool {
369
        applicableSessionKinds.contains { sessionKind in
370
            let text = completionCurrentTexts[sessionKind] ?? ""
371
            let normalized = text.trimmingCharacters(in: .whitespacesAndNewlines)
372
            return !normalized.isEmpty && parsedOptionalCurrent(text) == nil
373
        }
374
    }
375

            
376
    private func completionCurrentTextBinding(for sessionKind: ChargeSessionKind) -> Binding<String> {
377
        Binding(
378
            get: { completionCurrentTexts[sessionKind] ?? "" },
379
            set: { completionCurrentTexts[sessionKind] = $0 }
380
        )
381
    }
382

            
Bogdan Timofte authored a month ago
383
    private func save() {
384
        let didSave: Bool
385

            
386
        if kind == .charger {
387
            if let chargedDevice {
388
                didSave = appData.updateCharger(
389
                    id: chargedDevice.id,
390
                    name: name,
Bogdan Timofte authored a month ago
391
                    templateID: selectedTemplateID,
Bogdan Timofte authored a month ago
392
                    notes: notes
393
                )
394
            } else {
395
                didSave = appData.createCharger(
396
                    name: name,
Bogdan Timofte authored a month ago
397
                    templateID: selectedTemplateID,
Bogdan Timofte authored a month ago
398
                    notes: notes,
399
                    meterMACAddress: meterMACAddress
400
                )
401
            }
402
        } else {
403
            let configuredCompletionCurrents = parsedCompletionCurrents
404
            if let chargedDevice {
405
                didSave = appData.updateDevice(
406
                    id: chargedDevice.id,
407
                    name: name,
408
                    deviceClass: deviceClass,
Bogdan Timofte authored a month ago
409
                    templateID: selectedTemplateID,
Bogdan Timofte authored a month ago
410
                    chargingStateAvailability: chargingStateAvailability,
411
                    supportsWiredCharging: supportsWiredCharging,
412
                    supportsWirelessCharging: supportsWirelessCharging,
413
                    wirelessChargingProfile: wirelessChargingProfile,
414
                    configuredCompletionCurrents: configuredCompletionCurrents,
415
                    notes: notes
416
                )
417
            } else {
418
                didSave = appData.createDevice(
419
                    name: name,
420
                    deviceClass: deviceClass,
Bogdan Timofte authored a month ago
421
                    templateID: selectedTemplateID,
Bogdan Timofte authored a month ago
422
                    chargingStateAvailability: chargingStateAvailability,
423
                    supportsWiredCharging: supportsWiredCharging,
424
                    supportsWirelessCharging: supportsWirelessCharging,
425
                    wirelessChargingProfile: wirelessChargingProfile,
426
                    configuredCompletionCurrents: configuredCompletionCurrents,
427
                    notes: notes,
428
                    meterMACAddress: meterMACAddress
429
                )
430
            }
431
        }
432

            
433
        if didSave {
434
            dismiss()
435
        }
436
    }
437

            
Bogdan Timofte authored a month ago
438
    private func applyTemplateSelection(
439
        previousTemplateID: String?,
440
        newTemplateID: String?
441
    ) {
442
        guard let newTemplate = ChargedDeviceTemplateCatalog.shared.template(id: newTemplateID) else {
Bogdan Timofte authored a month ago
443
            return
444
        }
445

            
Bogdan Timofte authored a month ago
446
        let previousTemplate = ChargedDeviceTemplateCatalog.shared.template(id: previousTemplateID)
447
        let trimmedName = name.trimmingCharacters(in: .whitespacesAndNewlines)
448
        if trimmedName.isEmpty || trimmedName == previousTemplate?.name {
449
            name = newTemplate.name
450
        }
Bogdan Timofte authored a month ago
451

            
Bogdan Timofte authored a month ago
452
        deviceClass = newTemplate.deviceClass
453
        chargingStateAvailability = newTemplate.deviceClass.normalizedChargingStateAvailability(
454
            newTemplate.chargingStateAvailability
455
        )
456

            
457
        let normalizedChargingSupport = newTemplate.deviceClass.normalizedChargingSupport(
458
            supportsWiredCharging: newTemplate.supportsWiredCharging,
459
            supportsWirelessCharging: newTemplate.supportsWirelessCharging
460
        )
461
        supportsWiredCharging = normalizedChargingSupport.wired
462
        supportsWirelessCharging = normalizedChargingSupport.wireless
463
        wirelessChargingProfile = newTemplate.wirelessChargingProfile
464
    }
465

            
466
    private func applyDeviceClassRules(for deviceClass: ChargedDeviceClass) {
467
        if let selectedTemplate {
468
            chargingStateAvailability = deviceClass.normalizedChargingStateAvailability(
469
                selectedTemplate.chargingStateAvailability
470
            )
471
            let normalizedChargingSupport = deviceClass.normalizedChargingSupport(
472
                supportsWiredCharging: selectedTemplate.supportsWiredCharging,
473
                supportsWirelessCharging: selectedTemplate.supportsWirelessCharging
474
            )
475
            supportsWiredCharging = normalizedChargingSupport.wired
476
            supportsWirelessCharging = normalizedChargingSupport.wireless
477
            wirelessChargingProfile = selectedTemplate.wirelessChargingProfile
478
            return
479
        }
480

            
481
        if let enforcedChargingStateAvailability = deviceClass.enforcedChargingStateAvailability {
482
            chargingStateAvailability = enforcedChargingStateAvailability
483
        } else if chargedDevice == nil {
484
            chargingStateAvailability = Self.suggestedChargingStateAvailability(for: deviceClass)
485
        }
486

            
487
        if let enforcedChargingSupport = deviceClass.enforcedChargingSupport {
488
            supportsWiredCharging = enforcedChargingSupport.wired
489
            supportsWirelessCharging = enforcedChargingSupport.wireless
490
        } else if chargedDevice == nil {
491
            switch deviceClass {
492
            case .iphone:
493
                supportsWiredCharging = true
494
                supportsWirelessCharging = true
495
            case .watch:
496
                supportsWiredCharging = false
497
                supportsWirelessCharging = true
498
            case .powerbank:
499
                supportsWiredCharging = true
500
                supportsWirelessCharging = false
501
            case .charger:
502
                supportsWiredCharging = false
503
                supportsWirelessCharging = true
504
            case .other:
505
                supportsWiredCharging = true
506
                supportsWirelessCharging = false
507
            }
Bogdan Timofte authored a month ago
508
        }
509
    }
510

            
511
    private func parsedOptionalCurrent(_ text: String) -> Double? {
512
        let normalized = text
513
            .trimmingCharacters(in: .whitespacesAndNewlines)
514
            .replacingOccurrences(of: ",", with: ".")
515
        guard !normalized.isEmpty else {
516
            return nil
517
        }
Bogdan Timofte authored a month ago
518
        guard let value = Double(normalized), value > 0 else {
519
            return nil
520
        }
521
        return value
522
    }
523

            
Bogdan Timofte authored a month ago
524
    private func completionCurrentFieldLabel(for sessionKind: ChargeSessionKind) -> String {
525
        let showsTransport = supportedChargingModes.count > 1
526
        let showsState = chargingStateAvailability.supportedModes.count > 1
527

            
528
        switch (showsTransport, showsState) {
529
        case (true, true):
530
            return "\(sessionKind.shortTitle) completion current (A)"
531
        case (true, false):
532
            return "\(sessionKind.chargingTransportMode.title) completion current (A)"
533
        case (false, true):
534
            return "\(sessionKind.chargingStateMode.title) completion current (A)"
535
        case (false, false):
536
            return "Stop current (A)"
537
        }
538
    }
539

            
Bogdan Timofte authored a month ago
540
    private static func makeCompletionCurrentTexts(for chargedDevice: ChargedDeviceSummary?) -> [ChargeSessionKind: String] {
541
        guard let chargedDevice else {
542
            return [:]
543
        }
544

            
545
        return ChargeSessionKind.allCases.reduce(into: [ChargeSessionKind: String]()) { result, sessionKind in
546
            result[sessionKind] = optionalCurrentText(
547
                chargedDevice.configuredCompletionCurrentAmps(for: sessionKind)
548
            )
549
        }
Bogdan Timofte authored a month ago
550
    }
551

            
552
    private static func optionalCurrentText(_ value: Double?) -> String {
553
        guard let value else {
554
            return ""
555
        }
556
        return value.format(decimalDigits: 2)
557
    }
Bogdan Timofte authored a month ago
558

            
Bogdan Timofte authored a month ago
559
    private static func chargingSupportDescription(
560
        supportsWiredCharging: Bool,
561
        supportsWirelessCharging: Bool
562
    ) -> String {
563
        switch (supportsWiredCharging, supportsWirelessCharging) {
564
        case (true, true):
565
            return "Supports wired and wireless charging"
566
        case (true, false):
567
            return "Supports wired charging only"
568
        case (false, true):
569
            return "Supports wireless charging only"
570
        case (false, false):
571
            return "No charging method configured"
572
        }
573
    }
574

            
Bogdan Timofte authored a month ago
575
    private static func suggestedChargingStateAvailability(for deviceClass: ChargedDeviceClass) -> ChargingStateAvailability {
576
        switch deviceClass {
577
        case .iphone:
578
            return .onOrOff
579
        case .watch:
580
            return .onOnly
581
        case .powerbank:
582
            return .offOnly
Bogdan Timofte authored a month ago
583
        case .charger, .other:
Bogdan Timofte authored a month ago
584
            return .onOnly
585
        }
586
    }
Bogdan Timofte authored a month ago
587
}