// // ChargedDeviceEditorSheetView.swift // USB Meter // // Created by Codex on 10/04/2026. // import SwiftUI struct ChargedDeviceEditorSheetView: View { @EnvironmentObject private var appData: AppData @Environment(\.dismiss) private var dismiss let meterMACAddress: String? let chargedDevice: ChargedDeviceSummary? let suggestedDeviceClass: ChargedDeviceClass? @State private var name: String @State private var deviceClass: ChargedDeviceClass @State private var supportsChargingWhileOff: Bool @State private var supportsWiredCharging: Bool @State private var supportsWirelessCharging: Bool @State private var preferredChargingTransportMode: ChargingTransportMode @State private var wirelessChargingProfile: WirelessChargingProfile @State private var wiredChargeCompletionCurrentText: String @State private var wirelessChargeCompletionCurrentText: String @State private var notes: String init( meterMACAddress: String?, chargedDevice: ChargedDeviceSummary? = nil, suggestedDeviceClass: ChargedDeviceClass? = nil ) { self.meterMACAddress = meterMACAddress self.chargedDevice = chargedDevice self.suggestedDeviceClass = suggestedDeviceClass let initialDeviceClass = chargedDevice?.deviceClass ?? suggestedDeviceClass ?? .iphone _name = State(initialValue: chargedDevice?.name ?? "") _deviceClass = State(initialValue: initialDeviceClass) _supportsChargingWhileOff = State(initialValue: chargedDevice?.supportsChargingWhileOff ?? false) _supportsWiredCharging = State(initialValue: chargedDevice?.supportsWiredCharging ?? true) _supportsWirelessCharging = State(initialValue: chargedDevice?.supportsWirelessCharging ?? true) _preferredChargingTransportMode = State(initialValue: chargedDevice?.preferredChargingTransportMode ?? .wired) _wirelessChargingProfile = State(initialValue: chargedDevice?.wirelessChargingProfile ?? .genericQi) _wiredChargeCompletionCurrentText = State(initialValue: Self.optionalCurrentText(chargedDevice?.wiredChargeCompletionCurrentAmps)) _wirelessChargeCompletionCurrentText = State(initialValue: Self.optionalCurrentText(chargedDevice?.wirelessChargeCompletionCurrentAmps)) _notes = State(initialValue: chargedDevice?.notes ?? "") } var body: some View { NavigationView { Form { Section(header: Text("Identity")) { TextField("Name", text: $name) Picker("Class", selection: $deviceClass) { ForEach(ChargedDeviceClass.allCases) { deviceClass in Label(deviceClass.title, systemImage: deviceClass.symbolName) .tag(deviceClass) } } if let chargedDevice { Text(chargedDevice.qrIdentifier) .font(.caption.monospaced()) .foregroundColor(.secondary) .textSelection(.enabled) } } Section(header: Text("Charge Behaviour")) { Toggle("Can finish charging while device is off", isOn: $supportsChargingWhileOff) Text("This flag is used when we decide which charge sessions are reliable enough to estimate battery capacity.") .font(.footnote) .foregroundColor(.secondary) } Section(header: Text("Charging Support")) { Toggle("Supports wired charging", isOn: $supportsWiredCharging) Toggle("Supports wireless charging", isOn: $supportsWirelessCharging) if supportsWirelessCharging { Picker("Wireless profile", selection: $wirelessChargingProfile) { ForEach(WirelessChargingProfile.allCases) { profile in Text(profile.title) .tag(profile) } } Text(wirelessChargingProfile.description) .font(.footnote) .foregroundColor(.secondary) } if supportsWiredCharging || supportsWirelessCharging { Picker("Default session type", selection: preferredChargingTransportBinding) { ForEach(supportedChargingModes) { chargingTransportMode in Label(chargingTransportMode.title, systemImage: chargingTransportMode.symbolName) .tag(chargingTransportMode) } } } else { Text("Enable at least one charging method.") .font(.footnote) .foregroundColor(.secondary) } } Section(header: Text("Charge Completion")) { if supportsWiredCharging { TextField("Wired completion current (A)", text: $wiredChargeCompletionCurrentText) .keyboardType(.decimalPad) Text("Leave empty to keep learning this value from wired sessions.") .font(.footnote) .foregroundColor(.secondary) } if supportsWirelessCharging { TextField("Wireless completion current (A)", text: $wirelessChargeCompletionCurrentText) .keyboardType(.decimalPad) Text("Leave empty to keep learning this value from wireless sessions.") .font(.footnote) .foregroundColor(.secondary) } } Section(header: Text("Notes")) { TextField("Optional notes", text: $notes) } } .navigationTitle(editorTitle) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } } ToolbarItem(placement: .confirmationAction) { Button(saveButtonTitle) { let didSave: Bool if let chargedDevice { didSave = appData.updateChargedDevice( id: chargedDevice.id, name: name, deviceClass: deviceClass, supportsChargingWhileOff: supportsChargingWhileOff, supportsWiredCharging: supportsWiredCharging, supportsWirelessCharging: supportsWirelessCharging, preferredChargingTransportMode: resolvedPreferredChargingTransportMode, wirelessChargingProfile: wirelessChargingProfile, wiredChargeCompletionCurrentAmps: parsedOptionalCurrent(wiredChargeCompletionCurrentText), wirelessChargeCompletionCurrentAmps: parsedOptionalCurrent(wirelessChargeCompletionCurrentText), notes: notes ) } else { didSave = appData.createChargedDevice( name: name, deviceClass: deviceClass, supportsChargingWhileOff: supportsChargingWhileOff, supportsWiredCharging: supportsWiredCharging, supportsWirelessCharging: supportsWirelessCharging, preferredChargingTransportMode: resolvedPreferredChargingTransportMode, wirelessChargingProfile: wirelessChargingProfile, wiredChargeCompletionCurrentAmps: parsedOptionalCurrent(wiredChargeCompletionCurrentText), wirelessChargeCompletionCurrentAmps: parsedOptionalCurrent(wirelessChargeCompletionCurrentText), notes: notes, meterMACAddress: meterMACAddress ) } if didSave { dismiss() } } .disabled( name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || (!supportsWiredCharging && !supportsWirelessCharging) ) } } } .navigationViewStyle(StackNavigationViewStyle()) .onChange(of: deviceClass) { newValue in applySuggestedChargingSupport(for: newValue) } .onAppear { guard chargedDevice == nil else { return } applySuggestedChargingSupport(for: deviceClass) } } private var editorTitle: String { if chargedDevice == nil { return deviceClass == .charger ? "New Charger" : "New Device" } return chargedDevice?.isCharger == true ? "Edit Charger" : "Edit Device" } private var saveButtonTitle: String { chargedDevice == nil ? "Save" : "Update" } private var supportedChargingModes: [ChargingTransportMode] { var modes: [ChargingTransportMode] = [] if supportsWiredCharging { modes.append(.wired) } if supportsWirelessCharging { modes.append(.wireless) } return modes } private var resolvedPreferredChargingTransportMode: ChargingTransportMode { if supportedChargingModes.contains(preferredChargingTransportMode) { return preferredChargingTransportMode } return supportsWiredCharging ? .wired : .wireless } private var preferredChargingTransportBinding: Binding { Binding( get: { resolvedPreferredChargingTransportMode }, set: { preferredChargingTransportMode = $0 } ) } private func applySuggestedChargingSupport(for deviceClass: ChargedDeviceClass) { switch deviceClass { case .iphone: supportsWiredCharging = true supportsWirelessCharging = true preferredChargingTransportMode = .wired case .watch: supportsWiredCharging = false supportsWirelessCharging = true preferredChargingTransportMode = .wireless case .powerbank: supportsWiredCharging = true supportsWirelessCharging = false preferredChargingTransportMode = .wired case .charger: supportsWiredCharging = true supportsWirelessCharging = true preferredChargingTransportMode = .wireless case .other: supportsWiredCharging = true supportsWirelessCharging = false preferredChargingTransportMode = .wired } } private func parsedOptionalCurrent(_ text: String) -> Double? { let normalized = text .trimmingCharacters(in: .whitespacesAndNewlines) .replacingOccurrences(of: ",", with: ".") guard !normalized.isEmpty else { return nil } return Double(normalized) } private static func optionalCurrentText(_ value: Double?) -> String { guard let value else { return "" } return value.format(decimalDigits: 2) } }