Newer Older
203 lines | 6.67kb
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(appData: appData)
Bogdan Timofte authored a month ago
70
            }
71
        }
Bogdan Timofte authored 2 months ago
72
    }
73

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

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

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

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

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

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

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

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

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

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

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