USB-Meter / USB Meter / Views / Sidebar / SidebarList / Sections / SidebarUSBMetersSectionView.swift
1 contributor
134 lines | 5.04kb
//
//  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: MeterView(offlineSummary: meterSummary)) {
                                SidebarOfflineMeterCardView(summary: 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)
    }
}