// // ChargedDeviceLibrarySheetView.swift // USB Meter // // Created by Codex on 10/04/2026. // import SwiftUI enum ChargedDeviceLibraryMode { case device case charger var kind: ChargedDeviceKind { switch self { case .device: return .device case .charger: return .charger } } var title: String { kind.pluralTitle } var singularTitle: String { kind.title } } struct ChargedDeviceLibrarySheetView: View { @EnvironmentObject private var appData: AppData @Environment(\.dismiss) private var dismiss let meterMACAddress: String let meterTint: Color let mode: ChargedDeviceLibraryMode /// true = standalone sheet with own NavigationView; false = pushed into parent nav stack let standalone: Bool @State private var showingNewEditor = false @State private var editingChargedDevice: ChargedDeviceSummary? @State private var pendingDeletion: ChargedDeviceSummary? init( meterMACAddress: String, meterTint: Color, mode: ChargedDeviceLibraryMode, standalone: Bool = true ) { self.meterMACAddress = meterMACAddress self.meterTint = meterTint self.mode = mode self.standalone = standalone } var body: some View { if standalone { NavigationView { listContent } .navigationViewStyle(StackNavigationViewStyle()) } else { listContent } } private var listContent: some View { List { if displayedChargedDevices.isEmpty { VStack(alignment: .leading, spacing: 10) { HStack(spacing: 8) { Text("No \(mode.title.lowercased()) yet.") .font(.headline) ContextInfoButton( title: mode.title, message: emptyStateDescription ) } } .padding(.vertical, 10) .listRowBackground(Color.clear) } else { ForEach(displayedChargedDevices) { chargedDevice in Button { select(chargedDevice) dismiss() } label: { ChargedDeviceLibraryRowView( chargedDevice: chargedDevice, isSelected: chargedDevice.id == selectedDeviceID ) } .buttonStyle(.plain) .swipeActions(edge: .trailing, allowsFullSwipe: false) { Button(role: .destructive) { pendingDeletion = chargedDevice } label: { Label("Delete", systemImage: "trash") } Button { editingChargedDevice = chargedDevice } label: { Label("Edit", systemImage: "pencil") } .tint(.blue) } .contextMenu { Button { editingChargedDevice = chargedDevice } label: { Label("Edit \(mode.singularTitle)", systemImage: "pencil") } Button(role: .destructive) { pendingDeletion = chargedDevice } label: { Label("Delete \(mode.singularTitle)", systemImage: "trash") } } } } } .listStyle(InsetGroupedListStyle()) .background( LinearGradient( colors: [meterTint.opacity(0.14), Color.clear], startPoint: .topLeading, endPoint: .bottomTrailing ) .ignoresSafeArea() ) .navigationTitle(mode.title) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { if standalone { Button("Done") { dismiss() } } } ToolbarItem(placement: .confirmationAction) { Button("New") { showingNewEditor = true } } } .sheet(isPresented: $showingNewEditor) { newEditorSheet } .sheet(item: $editingChargedDevice) { device in editEditorSheet(device) } .confirmationDialog( "Delete \(pendingDeletion?.name ?? mode.singularTitle)?", isPresented: Binding( get: { pendingDeletion != nil }, set: { if !$0 { pendingDeletion = nil } } ), titleVisibility: .visible ) { Button("Delete", role: .destructive) { if let device = pendingDeletion { _ = appData.deleteChargedDevice(id: device.id) pendingDeletion = nil } } Button("Cancel", role: .cancel) { pendingDeletion = nil } } message: { Text("This will permanently remove the \(mode.singularTitle.lowercased()) and all associated data.") } } @ViewBuilder private var newEditorSheet: some View { if mode == .charger { ChargerEditorSheetView(meterMACAddress: meterMACAddress) .environmentObject(appData) } else { ChargedDeviceEditorSheetView(meterMACAddress: meterMACAddress) .environmentObject(appData) } } @ViewBuilder private func editEditorSheet(_ chargedDevice: ChargedDeviceSummary) -> some View { if chargedDevice.isCharger { ChargerEditorSheetView(chargedDevice: chargedDevice) .environmentObject(appData) } else { ChargedDeviceEditorSheetView( meterMACAddress: nil, chargedDevice: chargedDevice ) .environmentObject(appData) } } private var displayedChargedDevices: [ChargedDeviceSummary] { switch mode { case .device: return appData.deviceSummaries case .charger: return appData.chargerSummaries } } private var selectedDeviceID: UUID? { switch mode { case .device: return appData.currentChargedDeviceSummary(for: meterMACAddress)?.id case .charger: return appData.currentChargerSummary(for: meterMACAddress)?.id } } private var emptyStateDescription: String { switch mode { case .device: return "Create one here, then select it before or during a charging session. The selected device becomes the default for this meter." case .charger: return "Create one here, then select it for wireless charging sessions. The selected charger becomes the default wireless source for this meter." } } private func select(_ chargedDevice: ChargedDeviceSummary) { switch mode { case .device: appData.assignChargedDevice(chargedDevice.id, to: meterMACAddress) case .charger: appData.assignCharger(chargedDevice.id, to: meterMACAddress) } } }