// // SidebarView.swift // USB Meter // import SwiftUI import Combine private enum SidebarCreationSheet: Identifiable { case meter case device case charger var id: String { switch self { case .meter: return "meter" case .device: return "device" case .charger: return "charger" } } } struct SidebarView: View { @EnvironmentObject private var appData: AppData @State private var isHelpExpanded = false @State private var dismissedAutoHelpReason: SidebarHelpReason? @State private var now = Date() @State private var creationSheet: SidebarCreationSheet? private let helpRefreshTimer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() private let noDevicesHelpDelay: TimeInterval = 12 var body: some View { SidebarListView(backgroundTint: appData.bluetoothManager.managerState.color) { usbMetersSection } helpSection: { helpSection } debugSection: { debugSection } .onAppear { appData.bluetoothManager.start() now = Date() } .onReceive(helpRefreshTimer) { currentDate in now = currentDate } .onChange(of: activeHelpAutoReason) { newReason in if newReason == nil { dismissedAutoHelpReason = nil } } .sheet(item: $creationSheet) { sheet in switch sheet { case .meter: MeterEditorSheetView() .environmentObject(appData) case .device: ChargedDeviceEditorSheetView( meterMACAddress: nil ) .environmentObject(appData) case .charger: ChargerEditorSheetView(appData: appData) } } } private var usbMetersSection: some View { Group { SidebarUSBMetersSectionView( meters: appData.meterSummaries, managerState: appData.bluetoothManager.managerState, hasLiveMeters: appData.meters.isEmpty == false, scanStartedAt: appData.bluetoothManager.scanStartedAt, now: now, noDevicesHelpDelay: noDevicesHelpDelay, onAddMeter: { creationSheet = .meter } ) SidebarChargedDevicesSectionView( title: "Devices", mode: .device, chargedDevices: appData.deviceSummaries, emptyStateText: "No devices yet. Open Charge Record on a live meter or use the add button here to create one and start learning capacity.", tint: .orange, onAdd: { creationSheet = .device } ) SidebarChargedDevicesSectionView( title: "Chargers", mode: .charger, chargedDevices: appData.chargerSummaries, emptyStateText: "No chargers yet. Add one here so wireless sessions can track both the charged device and the charger being used.", tint: .pink, onAdd: { creationSheet = .charger } ) } } private var helpSection: some View { SidebarHelpSectionView( activeReason: activeHelpAutoReason, isExpanded: helpIsExpanded, bluetoothStatusTint: appData.bluetoothManager.managerState.color, bluetoothStatusText: bluetoothStatusText, cloudSyncHelpTitle: appData.cloudAvailability.helpTitle, cloudSyncHelpMessage: appData.cloudAvailability.helpMessage, onToggle: toggleHelpSection, onOpenSettings: openSettings ) { appData.bluetoothManager.managerState.helpView } deviceHelpDestination: { DeviceHelpView() } } private var debugSection: some View { SidebarDebugSectionView() } private var bluetoothStatusText: String { switch appData.bluetoothManager.managerState { case .poweredOff: return "Off" case .poweredOn: return "On" case .resetting: return "Resetting" case .unauthorized: return "Unauthorized" case .unknown: return "Unknown" case .unsupported: return "Unsupported" @unknown default: return "Other" } } private var helpIsExpanded: Bool { isHelpExpanded || shouldAutoExpandHelp } private var shouldAutoExpandHelp: Bool { guard let activeHelpAutoReason else { return false } return dismissedAutoHelpReason != activeHelpAutoReason } private var activeHelpAutoReason: SidebarHelpReason? { SidebarAutoHelpResolver.activeReason( managerState: appData.bluetoothManager.managerState, cloudAvailability: appData.cloudAvailability, hasLiveMeters: appData.meters.isEmpty == false, scanStartedAt: appData.bluetoothManager.scanStartedAt, now: now, noDevicesHelpDelay: noDevicesHelpDelay ) } private func toggleHelpSection() { withAnimation(.easeInOut(duration: 0.22)) { if shouldAutoExpandHelp { dismissedAutoHelpReason = activeHelpAutoReason isHelpExpanded = false } else { isHelpExpanded.toggle() } } } private func openSettings() { guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { return } UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil) } }