// // SidebarUSBMetersSectionView.swift // USB Meter // import SwiftUI import CoreBluetooth struct SidebarUSBMetersSectionView: View { let meters: [AppData.MeterSummary] let managerState: CBManagerState let hasLiveMeters: Bool let scanStartedAt: Date? let now: Date let noDevicesHelpDelay: TimeInterval let isExpanded: Bool let onToggle: () -> Void let onAddMeter: () -> Void var body: some View { Section(header: usbSectionHeader) { if isExpanded { if meters.isEmpty { Text(devicesEmptyStateText) .font(.footnote) .foregroundColor(.secondary) .frame(maxWidth: .infinity, alignment: .leading) .padding(18) .meterCard( tint: isWaitingForFirstDiscovery ? .blue : .secondary, fillOpacity: 0.14, strokeOpacity: 0.20 ) .transition(.opacity.combined(with: .move(edge: .top))) } else { ForEach(meters) { meterSummary in if let meter = meterSummary.meter { NavigationLink(destination: MeterView().environmentObject(meter)) { SidebarMeterCardView() .environmentObject(meter) } .buttonStyle(.plain) .transition(.opacity.combined(with: .move(edge: .top))) } else { NavigationLink(destination: MeterDetailView(meterSummary: meterSummary)) { MeterCardView(meterSummary: meterSummary) } .buttonStyle(.plain) .transition(.opacity.combined(with: .move(edge: .top))) } } } } } } private var isWaitingForFirstDiscovery: Bool { guard managerState == .poweredOn else { return false } guard hasLiveMeters == false else { return false } guard let scanStartedAt else { return false } return now.timeIntervalSince(scanStartedAt) < noDevicesHelpDelay } private var devicesEmptyStateText: String { if isWaitingForFirstDiscovery { return "Scanning for nearby supported meters..." } return "No meters yet. Nearby supported meters will appear here and remain available after they disappear." } private var usbSectionHeader: some View { HStack(alignment: .firstTextBaseline) { Button(action: onToggle) { HStack(alignment: .firstTextBaseline, spacing: 4) { Image(systemName: "chevron.right") .font(.caption.weight(.semibold)) .foregroundColor(.secondary) .rotationEffect(.degrees(isExpanded ? 90 : 0)) .animation(.easeInOut(duration: 0.22), value: isExpanded) VStack(alignment: .leading, spacing: 2) { Text("USB & Known Meters") .font(.headline) if meters.isEmpty == false { Text(sectionSubtitleText) .font(.caption) .foregroundColor(.secondary) .lineLimit(1) } } } } .buttonStyle(.plain) Spacer() Button(action: onAddMeter) { Image(systemName: "plus.circle.fill") .font(.body.weight(.semibold)) .foregroundColor(.blue) } .buttonStyle(.plain) Text("\(meters.count)") .font(.caption.weight(.bold)) .padding(.horizontal, 10) .padding(.vertical, 6) .meterCard(tint: .blue, fillOpacity: 0.18, strokeOpacity: 0.24, cornerRadius: 999) } } private var sectionSubtitleText: String { switch (liveMeterCount, offlineMeterCount) { case let (live, offline) where live > 0 && offline > 0: return "\(live) live • \(offline) stored" case let (live, _) where live > 0: return "\(live) live meter\(live == 1 ? "" : "s")" case let (_, offline) where offline > 0: return "\(offline) known meter\(offline == 1 ? "" : "s")" default: return "" } } private var liveMeterCount: Int { meters.filter { $0.meter != nil }.count } private var offlineMeterCount: Int { max(0, meters.count - liveMeterCount) } }