Newer Older
204 lines | 6.702kb
Bogdan Timofte authored 2 months ago
1
//
2
//  SidebarView.swift
3
//  USB Meter
4
//
5

            
6
import SwiftUI
7
import Combine
8

            
Bogdan Timofte authored a month ago
9
private enum SidebarCreationSheet: Identifiable {
10
    case meter
11
    case device
12
    case charger
13

            
14
    var id: String {
15
        switch self {
16
        case .meter:
17
            return "meter"
18
        case .device:
19
            return "device"
20
        case .charger:
21
            return "charger"
22
        }
23
    }
24
}
25

            
Bogdan Timofte authored 2 months ago
26
struct SidebarView: View {
27
    @EnvironmentObject private var appData: AppData
Bogdan Timofte authored a month ago
28
    @State private var isUSBMetersExpanded = true
29
    @State private var isDevicesExpanded = true
30
    @State private var isChargersExpanded = true
Bogdan Timofte authored 2 months ago
31
    @State private var isHelpExpanded = false
32
    @State private var dismissedAutoHelpReason: SidebarHelpReason?
33
    @State private var now = Date()
Bogdan Timofte authored a month ago
34
    @State private var creationSheet: SidebarCreationSheet?
Bogdan Timofte authored 2 months ago
35
    private let helpRefreshTimer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
36
    private let noDevicesHelpDelay: TimeInterval = 12
37

            
38
    var body: some View {
39
        SidebarListView(backgroundTint: appData.bluetoothManager.managerState.color) {
40
            usbMetersSection
41
        } helpSection: {
42
            helpSection
43
        } debugSection: {
44
            debugSection
45
        }
46
        .onAppear {
47
            appData.bluetoothManager.start()
48
            now = Date()
49
        }
50
        .onReceive(helpRefreshTimer) { currentDate in
51
            now = currentDate
52
        }
53
        .onChange(of: activeHelpAutoReason) { newReason in
54
            if newReason == nil {
55
                dismissedAutoHelpReason = nil
56
            }
57
        }
Bogdan Timofte authored a month ago
58
        .sheet(item: $creationSheet) { sheet in
59
            switch sheet {
60
            case .meter:
61
                MeterEditorSheetView()
62
                    .environmentObject(appData)
63
            case .device:
64
                ChargedDeviceEditorSheetView(
Bogdan Timofte authored a month ago
65
                    meterMACAddress: nil
Bogdan Timofte authored a month ago
66
                )
67
                .environmentObject(appData)
68
            case .charger:
Bogdan Timofte authored a month ago
69
                ChargerEditorSheetView()
70
                    .environmentObject(appData)
Bogdan Timofte authored a month ago
71
            }
72
        }
Bogdan Timofte authored 2 months ago
73
    }
74

            
75
    private var usbMetersSection: some View {
Bogdan Timofte authored a month ago
76
        Group {
77
            SidebarUSBMetersSectionView(
78
                meters: appData.meterSummaries,
79
                managerState: appData.bluetoothManager.managerState,
80
                hasLiveMeters: appData.meters.isEmpty == false,
81
                scanStartedAt: appData.bluetoothManager.scanStartedAt,
82
                now: now,
83
                noDevicesHelpDelay: noDevicesHelpDelay,
Bogdan Timofte authored a month ago
84
                isExpanded: isUSBMetersExpanded,
85
                onToggle: {
86
                    withAnimation(.easeInOut(duration: 0.22)) {
87
                        isUSBMetersExpanded.toggle()
88
                    }
89
                },
Bogdan Timofte authored a month ago
90
                onAddMeter: { creationSheet = .meter }
91
            )
92

            
93
            SidebarChargedDevicesSectionView(
94
                title: "Devices",
Bogdan Timofte authored a month ago
95
                mode: .device,
Bogdan Timofte authored a month ago
96
                chargedDevices: appData.deviceSummaries,
97
                emptyStateText: "No devices yet. Open Charge Record on a live meter or use the add button here to create one and start learning capacity.",
98
                tint: .orange,
Bogdan Timofte authored a month ago
99
                isExpanded: isDevicesExpanded,
100
                onToggle: {
101
                    withAnimation(.easeInOut(duration: 0.22)) {
102
                        isDevicesExpanded.toggle()
103
                    }
104
                },
Bogdan Timofte authored a month ago
105
                onAdd: { creationSheet = .device }
106
            )
107

            
108
            SidebarChargedDevicesSectionView(
109
                title: "Chargers",
Bogdan Timofte authored a month ago
110
                mode: .charger,
Bogdan Timofte authored a month ago
111
                chargedDevices: appData.chargerSummaries,
112
                emptyStateText: "No chargers yet. Add one here so wireless sessions can track both the charged device and the charger being used.",
113
                tint: .pink,
Bogdan Timofte authored a month ago
114
                isExpanded: isChargersExpanded,
115
                onToggle: {
116
                    withAnimation(.easeInOut(duration: 0.22)) {
117
                        isChargersExpanded.toggle()
118
                    }
119
                },
Bogdan Timofte authored a month ago
120
                onAdd: { creationSheet = .charger }
121
            )
122
        }
Bogdan Timofte authored 2 months ago
123
    }
124

            
125
    private var helpSection: some View {
126
        SidebarHelpSectionView(
127
            activeReason: activeHelpAutoReason,
128
            isExpanded: helpIsExpanded,
129
            bluetoothStatusTint: appData.bluetoothManager.managerState.color,
130
            bluetoothStatusText: bluetoothStatusText,
131
            cloudSyncHelpTitle: appData.cloudAvailability.helpTitle,
132
            cloudSyncHelpMessage: appData.cloudAvailability.helpMessage,
133
            onToggle: toggleHelpSection,
134
            onOpenSettings: openSettings
135
        ) {
136
            appData.bluetoothManager.managerState.helpView
137
        } deviceHelpDestination: {
138
            DeviceHelpView()
139
        }
140
    }
141

            
142
    private var debugSection: some View {
143
        SidebarDebugSectionView()
144
    }
145

            
146
    private var bluetoothStatusText: String {
147
        switch appData.bluetoothManager.managerState {
148
        case .poweredOff:
149
            return "Off"
150
        case .poweredOn:
151
            return "On"
152
        case .resetting:
153
            return "Resetting"
154
        case .unauthorized:
155
            return "Unauthorized"
156
        case .unknown:
157
            return "Unknown"
158
        case .unsupported:
159
            return "Unsupported"
160
        @unknown default:
161
            return "Other"
162
        }
163
    }
164

            
165
    private var helpIsExpanded: Bool {
166
        isHelpExpanded || shouldAutoExpandHelp
167
    }
168

            
169
    private var shouldAutoExpandHelp: Bool {
170
        guard let activeHelpAutoReason else {
171
            return false
172
        }
173
        return dismissedAutoHelpReason != activeHelpAutoReason
174
    }
175

            
176
    private var activeHelpAutoReason: SidebarHelpReason? {
177
        SidebarAutoHelpResolver.activeReason(
178
            managerState: appData.bluetoothManager.managerState,
179
            cloudAvailability: appData.cloudAvailability,
180
            hasLiveMeters: appData.meters.isEmpty == false,
181
            scanStartedAt: appData.bluetoothManager.scanStartedAt,
182
            now: now,
183
            noDevicesHelpDelay: noDevicesHelpDelay
184
        )
185
    }
186

            
187
    private func toggleHelpSection() {
188
        withAnimation(.easeInOut(duration: 0.22)) {
189
            if shouldAutoExpandHelp {
190
                dismissedAutoHelpReason = activeHelpAutoReason
191
                isHelpExpanded = false
192
            } else {
193
                isHelpExpanded.toggle()
194
            }
195
        }
196
    }
197

            
198
    private func openSettings() {
199
        guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else {
200
            return
201
        }
202
        UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil)
203
    }
204
}