1 contributor
//
// 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 {
switch self {
case .device:
return "Devices"
case .charger:
return "Chargers"
}
}
var singularTitle: String {
switch self {
case .device:
return "Device"
case .charger:
return "Charger"
}
}
}
struct ChargedDeviceLibrarySheetView: View {
@EnvironmentObject private var appData: AppData
@Binding var visibility: Bool
let meterMACAddress: String
let meterTint: Color
let mode: ChargedDeviceLibraryMode
@State private var editorVisibility = false
@State private var editingChargedDevice: ChargedDeviceSummary?
var body: some View {
NavigationView {
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)
visibility = false
} label: {
ChargedDeviceLibraryRowView(
chargedDevice: chargedDevice,
isSelected: chargedDevice.id == selectedDeviceID
)
}
.buttonStyle(.plain)
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
Button {
editingChargedDevice = chargedDevice
} label: {
Label("Edit", systemImage: "pencil")
}
.tint(.blue)
}
.contextMenu {
Button {
editingChargedDevice = chargedDevice
} label: {
Label("Edit \(mode.singularTitle)", systemImage: "pencil")
}
}
}
}
}
.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) {
Button("Done") {
visibility = false
}
}
ToolbarItem(placement: .confirmationAction) {
Button("New") {
editorVisibility = true
}
}
}
}
.navigationViewStyle(StackNavigationViewStyle())
.sheet(isPresented: $editorVisibility) {
ChargedDeviceEditorSheetView(
meterMACAddress: meterMACAddress,
kind: mode.kind
)
.environmentObject(appData)
}
.sheet(item: $editingChargedDevice) { chargedDevice in
ChargedDeviceEditorSheetView(
meterMACAddress: nil,
kind: mode.kind,
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)
}
}
}
private struct ChargedDeviceLibraryRowView: View {
let chargedDevice: ChargedDeviceSummary
let isSelected: Bool
var body: some View {
HStack(alignment: .top, spacing: 14) {
ChargedDeviceQRCodeView(qrIdentifier: chargedDevice.qrIdentifier, side: 58)
VStack(alignment: .leading, spacing: 6) {
HStack {
Label(chargedDevice.name, systemImage: chargedDevice.identitySymbolName)
.font(.headline)
.foregroundColor(.primary)
Spacer()
if isSelected {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
}
}
Text(chargedDevice.identityTitle)
.font(.caption.weight(.semibold))
.foregroundColor(.secondary)
if chargedDevice.isCharger {
if !chargedDevice.chargerObservedVoltageSelections.isEmpty {
Text(
chargedDevice.chargerObservedVoltageSelections
.map { "\($0.format(decimalDigits: 1)) V" }
.joined(separator: ", ")
)
.font(.caption2)
.foregroundColor(.secondary)
} else if let chargerMaximumPowerWatts = chargedDevice.chargerMaximumPowerWatts {
Text("Max power: \(chargerMaximumPowerWatts.format(decimalDigits: 2)) W")
.font(.caption2)
.foregroundColor(.secondary)
} else {
Text("Wireless charger")
.font(.caption2)
.foregroundColor(.secondary)
}
} else {
Text(chargedDevice.supportedChargingModes.map(\.title).joined(separator: " + "))
.font(.caption2)
.foregroundColor(.secondary)
if let capacity = chargedDevice.estimatedBatteryCapacityWh {
Text("Estimated capacity: \(capacity.format(decimalDigits: 2)) Wh")
.font(.caption)
.foregroundColor(.secondary)
}
if let minimumCurrent = chargedDevice.minimumCurrentAmps {
Text("Completion current: \(minimumCurrent.format(decimalDigits: 2)) A")
.font(.caption2)
.foregroundColor(.secondary)
}
}
}
}
.padding(.vertical, 4)
}
}