USB-Meter / USB Meter / Views / ChargedDevices / SidebarChargedDevicesSectionView.swift
Newer Older
150 lines | 5.745kb
Bogdan Timofte authored a month ago
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
Bogdan Timofte authored a month ago
12
    let mode: ChargedDeviceLibraryMode
Bogdan Timofte authored a month ago
13
    let chargedDevices: [ChargedDeviceSummary]
14
    let emptyStateText: String
15
    let tint: Color
Bogdan Timofte authored a month ago
16
    let isExpanded: Bool
17
    let onToggle: () -> Void
Bogdan Timofte authored a month ago
18
    let onAdd: () -> Void
19

            
20
    var body: some View {
21
        Section(header: headerView) {
Bogdan Timofte authored a month ago
22
            if isExpanded {
23
                // Library overview row — navigates to the full management library
24
                NavigationLink(destination: SidebarChargedDeviceLibraryView(mode: mode)) {
25
                    libraryRow
Bogdan Timofte authored a month ago
26
                }
Bogdan Timofte authored a month ago
27
                .buttonStyle(.plain)
Bogdan Timofte authored a month ago
28
                .transition(.opacity.combined(with: .move(edge: .top)))
29

            
30
                ForEach(chargedDevices) { chargedDevice in
31
                    NavigationLink(destination: ChargedDeviceDetailView(chargedDeviceID: chargedDevice.id)) {
32
                        ChargedDeviceSidebarCardView(chargedDevice: chargedDevice)
33
                    }
34
                    .buttonStyle(.plain)
35
                    .transition(.opacity.combined(with: .move(edge: .top)))
36
                }
Bogdan Timofte authored a month ago
37
            }
38
        }
39
    }
40

            
41
    private var libraryRow: some View {
42
        HStack(spacing: 12) {
43
            Image(systemName: mode == .device ? "square.grid.2x2" : "bolt.circle")
44
                .font(.system(size: 16, weight: .semibold))
45
                .foregroundColor(tint)
46
                .frame(width: 36, height: 36)
47
                .background(tint.opacity(0.14))
48
                .clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
49

            
50
            VStack(alignment: .leading, spacing: 2) {
51
                Text("All \(title)")
52
                    .font(.subheadline.weight(.semibold))
53
                    .foregroundColor(.primary)
54
                let count = chargedDevices.count
55
                Text("\(count) \(count == 1 ? mode.singularTitle.lowercased() : mode.title.lowercased())")
56
                    .font(.caption)
57
                    .foregroundColor(.secondary)
Bogdan Timofte authored a month ago
58
            }
Bogdan Timofte authored a month ago
59

            
60
            Spacer()
Bogdan Timofte authored a month ago
61
        }
Bogdan Timofte authored a month ago
62
        .padding(.vertical, 4)
Bogdan Timofte authored a month ago
63
    }
64

            
65
    private var headerView: some View {
66
        HStack(alignment: .firstTextBaseline, spacing: 10) {
Bogdan Timofte authored a month ago
67
            Button(action: onToggle) {
68
                HStack(alignment: .firstTextBaseline, spacing: 4) {
69
                    Image(systemName: "chevron.right")
70
                        .font(.caption.weight(.semibold))
71
                        .foregroundColor(.secondary)
72
                        .rotationEffect(.degrees(isExpanded ? 90 : 0))
73
                        .animation(.easeInOut(duration: 0.22), value: isExpanded)
74
                    Text(title)
75
                        .font(.headline)
76
                }
77
            }
78
            .buttonStyle(.plain)
Bogdan Timofte authored a month ago
79
            Spacer()
80
            Button(action: onAdd) {
81
                Image(systemName: "plus.circle.fill")
82
                    .font(.body.weight(.semibold))
83
                    .foregroundColor(tint)
84
            }
85
            .buttonStyle(.plain)
86
            Text("\(chargedDevices.count)")
87
                .font(.caption.weight(.bold))
88
                .padding(.horizontal, 10)
89
                .padding(.vertical, 6)
90
                .meterCard(tint: tint, fillOpacity: 0.18, strokeOpacity: 0.24, cornerRadius: 999)
91
        }
92
    }
93
}
94

            
95
private struct ChargedDeviceSidebarCardView: View {
96
    let chargedDevice: ChargedDeviceSummary
97

            
98
    var body: some View {
99
        HStack(alignment: .top, spacing: 12) {
100
            ChargedDeviceQRCodeView(qrIdentifier: chargedDevice.qrIdentifier, side: 54)
101

            
102
            VStack(alignment: .leading, spacing: 6) {
103
                HStack {
Bogdan Timofte authored a month ago
104
                    ChargedDeviceIdentityLabelView(
105
                        chargedDevice: chargedDevice,
106
                        iconPointSize: 17
107
                    )
108
                    .font(.headline)
Bogdan Timofte authored a month ago
109
                    if chargedDevice.activeSession != nil {
110
                        Spacer()
111
                        Text("Live")
112
                            .font(.caption.weight(.bold))
113
                            .foregroundColor(.green)
114
                    }
115
                }
116

            
Bogdan Timofte authored a month ago
117
                Text(chargedDevice.identityTitle)
Bogdan Timofte authored a month ago
118
                    .font(.caption.weight(.semibold))
119
                    .foregroundColor(.secondary)
120

            
Bogdan Timofte authored a month ago
121
                if chargedDevice.isCharger {
122
                    if let chargerMaximumPowerWatts = chargedDevice.chargerMaximumPowerWatts {
123
                        Text("Max power: \(chargerMaximumPowerWatts.format(decimalDigits: 2)) W")
124
                            .font(.caption2)
125
                            .foregroundColor(.secondary)
126
                    } else {
127
                        Text("Wireless charger")
128
                            .font(.caption2)
129
                            .foregroundColor(.secondary)
130
                    }
Bogdan Timofte authored a month ago
131
                } else {
Bogdan Timofte authored a month ago
132
                    Text(chargedDevice.supportedChargingModes.map(\.title).joined(separator: " + "))
Bogdan Timofte authored a month ago
133
                        .font(.caption2)
134
                        .foregroundColor(.secondary)
Bogdan Timofte authored a month ago
135

            
136
                    if let estimatedCapacityWh = chargedDevice.estimatedBatteryCapacityWh {
137
                        Text("Capacity: \(estimatedCapacityWh.format(decimalDigits: 2)) Wh")
138
                            .font(.caption2)
139
                            .foregroundColor(.secondary)
140
                    } else {
141
                        Text("Capacity: learning")
142
                            .font(.caption2)
143
                            .foregroundColor(.secondary)
144
                    }
Bogdan Timofte authored a month ago
145
                }
146
            }
147
        }
148
        .padding(.vertical, 4)
149
    }
150
}