| 1 |
// |
|
| 2 |
// SidebarChargedDevicesSectionView.swift |
|
| 3 |
// USB Meter |
|
| 4 |
// |
|
| 5 |
// Created by Codex on 10/04/2026. |
|
| 6 |
// |
|
| 7 | ||
| 8 |
import SwiftUI |
|
| 9 | ||
| 10 |
struct SidebarChargedDevicesSectionView: View {
|
|
| 11 |
let title: String |
|
| 12 |
let chargedDevices: [ChargedDeviceSummary] |
|
| 13 |
let emptyStateText: String |
|
| 14 |
let tint: Color |
|
| 15 |
let onAdd: () -> Void |
|
| 16 | ||
| 17 |
var body: some View {
|
|
| 18 |
Section(header: headerView) {
|
|
| 19 |
if chargedDevices.isEmpty {
|
|
| 20 |
Text(emptyStateText) |
|
| 21 |
.font(.footnote) |
|
| 22 |
.foregroundColor(.secondary) |
|
| 23 |
.frame(maxWidth: .infinity, alignment: .leading) |
|
| 24 |
.padding(18) |
|
| 25 |
.meterCard(tint: tint, fillOpacity: 0.12, strokeOpacity: 0.18) |
|
| 26 |
} else {
|
|
| 27 |
ForEach(chargedDevices) { chargedDevice in
|
|
| 28 |
NavigationLink(destination: ChargedDeviceDetailView(chargedDeviceID: chargedDevice.id)) {
|
|
| 29 |
ChargedDeviceSidebarCardView(chargedDevice: chargedDevice) |
|
| 30 |
} |
|
| 31 |
.buttonStyle(.plain) |
|
| 32 |
} |
|
| 33 |
} |
|
| 34 |
} |
|
| 35 |
} |
|
| 36 | ||
| 37 |
private var headerView: some View {
|
|
| 38 |
HStack(alignment: .firstTextBaseline, spacing: 10) {
|
|
| 39 |
Text(title) |
|
| 40 |
.font(.headline) |
|
| 41 |
Spacer() |
|
| 42 |
Button(action: onAdd) {
|
|
| 43 |
Image(systemName: "plus.circle.fill") |
|
| 44 |
.font(.body.weight(.semibold)) |
|
| 45 |
.foregroundColor(tint) |
|
| 46 |
} |
|
| 47 |
.buttonStyle(.plain) |
|
| 48 |
Text("\(chargedDevices.count)")
|
|
| 49 |
.font(.caption.weight(.bold)) |
|
| 50 |
.padding(.horizontal, 10) |
|
| 51 |
.padding(.vertical, 6) |
|
| 52 |
.meterCard(tint: tint, fillOpacity: 0.18, strokeOpacity: 0.24, cornerRadius: 999) |
|
| 53 |
} |
|
| 54 |
} |
|
| 55 |
} |
|
| 56 | ||
| 57 |
private struct ChargedDeviceSidebarCardView: View {
|
|
| 58 |
let chargedDevice: ChargedDeviceSummary |
|
| 59 | ||
| 60 |
var body: some View {
|
|
| 61 |
HStack(alignment: .top, spacing: 12) {
|
|
| 62 |
ChargedDeviceQRCodeView(qrIdentifier: chargedDevice.qrIdentifier, side: 54) |
|
| 63 | ||
| 64 |
VStack(alignment: .leading, spacing: 6) {
|
|
| 65 |
HStack {
|
|
| 66 |
Label(chargedDevice.name, systemImage: chargedDevice.deviceClass.symbolName) |
|
| 67 |
.font(.headline) |
|
| 68 |
if chargedDevice.activeSession != nil {
|
|
| 69 |
Spacer() |
|
| 70 |
Text("Live")
|
|
| 71 |
.font(.caption.weight(.bold)) |
|
| 72 |
.foregroundColor(.green) |
|
| 73 |
} |
|
| 74 |
} |
|
| 75 | ||
| 76 |
Text(chargedDevice.deviceClass.title) |
|
| 77 |
.font(.caption.weight(.semibold)) |
|
| 78 |
.foregroundColor(.secondary) |
|
| 79 | ||
| 80 |
Text(chargedDevice.supportedChargingModes.map(\.title).joined(separator: " + ")) |
|
| 81 |
.font(.caption2) |
|
| 82 |
.foregroundColor(.secondary) |
|
| 83 | ||
| 84 |
if let estimatedCapacityWh = chargedDevice.estimatedBatteryCapacityWh {
|
|
| 85 |
Text("Capacity: \(estimatedCapacityWh.format(decimalDigits: 2)) Wh")
|
|
| 86 |
.font(.caption2) |
|
| 87 |
.foregroundColor(.secondary) |
|
| 88 |
} else if chargedDevice.isCharger, let chargerMaximumPowerWatts = chargedDevice.chargerMaximumPowerWatts {
|
|
| 89 |
Text("Max power: \(chargerMaximumPowerWatts.format(decimalDigits: 2)) W")
|
|
| 90 |
.font(.caption2) |
|
| 91 |
.foregroundColor(.secondary) |
|
| 92 |
} else {
|
|
| 93 |
Text("Capacity: learning")
|
|
| 94 |
.font(.caption2) |
|
| 95 |
.foregroundColor(.secondary) |
|
| 96 |
} |
|
| 97 |
} |
|
| 98 |
} |
|
| 99 |
.padding(.vertical, 4) |
|
| 100 |
} |
|
| 101 |
} |