1 contributor
204 lines | 6.702kb
//
//  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 isUSBMetersExpanded = true
    @State private var isDevicesExpanded = true
    @State private var isChargersExpanded = true
    @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()
                    .environmentObject(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,
                isExpanded: isUSBMetersExpanded,
                onToggle: {
                    withAnimation(.easeInOut(duration: 0.22)) {
                        isUSBMetersExpanded.toggle()
                    }
                },
                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,
                isExpanded: isDevicesExpanded,
                onToggle: {
                    withAnimation(.easeInOut(duration: 0.22)) {
                        isDevicesExpanded.toggle()
                    }
                },
                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,
                isExpanded: isChargersExpanded,
                onToggle: {
                    withAnimation(.easeInOut(duration: 0.22)) {
                        isChargersExpanded.toggle()
                    }
                },
                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)
    }
}