1 contributor
//
// 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)
}
}