USB-Meter / USB Meter / Views / ChargedDevices / Sidebar / SidebarChargedDeviceLibraryView.swift
Newer Older
158 lines | 5.317kb
Bogdan Timofte authored a month ago
1
//
2
//  SidebarChargedDeviceLibraryView.swift
3
//  USB Meter
4
//
5
//  Created by Codex on 22/04/2026.
6
//
7

            
8
import SwiftUI
9

            
10
/// Full-management library for the sidebar — navigates into detail instead of select-and-dismiss.
11
struct SidebarChargedDeviceLibraryView: View {
12
    @EnvironmentObject private var appData: AppData
13

            
14
    let mode: ChargedDeviceLibraryMode
15

            
16
    @State private var showingNewEditor = false
17
    @State private var editingChargedDevice: ChargedDeviceSummary?
18
    @State private var pendingDeletion: ChargedDeviceSummary?
19

            
20
    private var tint: Color {
21
        mode == .device ? .orange : .pink
22
    }
23

            
24
    var body: some View {
25
        List {
26
            if displayedDevices.isEmpty {
27
                emptyStateView
28
            } else {
29
                deviceRows
30
            }
31
        }
32
        .listStyle(InsetGroupedListStyle())
33
        .background(backgroundGradient)
34
        .navigationTitle(mode.title)
35
        .navigationBarTitleDisplayMode(.inline)
36
        .toolbar {
37
            ToolbarItem(placement: .primaryAction) {
38
                Button("New") { showingNewEditor = true }
39
            }
40
        }
41
        .sheet(isPresented: $showingNewEditor) { newEditorSheet }
42
        .sheet(item: $editingChargedDevice) { device in editEditorSheet(device) }
43
        .confirmationDialog(
44
            "Delete \(pendingDeletion?.name ?? mode.singularTitle)?",
45
            isPresented: Binding(
46
                get: { pendingDeletion != nil },
47
                set: { if !$0 { pendingDeletion = nil } }
48
            ),
49
            titleVisibility: .visible
50
        ) {
51
            Button("Delete", role: .destructive, action: deletePendingDevice)
52
            Button("Cancel", role: .cancel) { pendingDeletion = nil }
53
        } message: {
54
            Text("This will permanently remove the \(mode.singularTitle.lowercased()) and all associated data.")
55
        }
56
    }
57

            
58
    private var emptyStateView: some View {
59
        VStack(alignment: .leading, spacing: 10) {
60
            HStack(spacing: 8) {
61
                Text("No \(mode.title.lowercased()) yet.")
62
                    .font(.headline)
63
                ContextInfoButton(
64
                    title: mode.title,
65
                    message: emptyStateDescription
66
                )
67
            }
68
        }
69
        .padding(.vertical, 10)
70
        .listRowBackground(Color.clear)
71
    }
72

            
73
    private var deviceRows: some View {
74
        ForEach(displayedDevices) { device in
75
            NavigationLink(destination: ChargedDeviceDetailView(chargedDeviceID: device.id)) {
76
                ChargedDeviceLibraryRowView(chargedDevice: device, isSelected: false)
77
            }
78
            .swipeActions(edge: .trailing, allowsFullSwipe: false) {
79
                rowActions(for: device)
80
            }
81
            .contextMenu {
82
                Button {
83
                    editingChargedDevice = device
84
                } label: {
85
                    Label("Edit \(mode.singularTitle)", systemImage: "pencil")
86
                }
87
                Button(role: .destructive) {
88
                    pendingDeletion = device
89
                } label: {
90
                    Label("Delete \(mode.singularTitle)", systemImage: "trash")
91
                }
92
            }
93
        }
94
    }
95

            
96
    private var backgroundGradient: some View {
97
        LinearGradient(
98
            colors: [tint.opacity(0.14), Color.clear],
99
            startPoint: .topLeading,
100
            endPoint: .bottomTrailing
101
        )
102
        .ignoresSafeArea()
103
    }
104

            
105
    private var displayedDevices: [ChargedDeviceSummary] {
106
        mode == .device ? appData.deviceSummaries : appData.chargerSummaries
107
    }
108

            
109
    @ViewBuilder
110
    private var newEditorSheet: some View {
111
        if mode == .charger {
112
            ChargerEditorSheetView(meterMACAddress: nil)
113
                .environmentObject(appData)
114
        } else {
115
            ChargedDeviceEditorSheetView(meterMACAddress: nil)
116
                .environmentObject(appData)
117
        }
118
    }
119

            
120
    @ViewBuilder
121
    private func editEditorSheet(_ device: ChargedDeviceSummary) -> some View {
122
        if device.isCharger {
123
            ChargerEditorSheetView(chargedDevice: device)
124
                .environmentObject(appData)
125
        } else {
126
            ChargedDeviceEditorSheetView(meterMACAddress: nil, chargedDevice: device)
127
                .environmentObject(appData)
128
        }
129
    }
130

            
131
    @ViewBuilder
132
    private func rowActions(for device: ChargedDeviceSummary) -> some View {
133
        Button(role: .destructive) {
134
            pendingDeletion = device
135
        } label: {
136
            Label("Delete", systemImage: "trash")
137
        }
138
        Button {
139
            editingChargedDevice = device
140
        } label: {
141
            Label("Edit", systemImage: "pencil")
142
        }
143
        .tint(.blue)
144
    }
145

            
146
    private var emptyStateDescription: String {
147
        mode == .device
148
            ? "Create one here, then select it before or during a charging session. The selected device becomes the default for this meter."
149
            : "Create one here, then select it for wireless charging sessions. The selected charger becomes the default wireless source for this meter."
150
    }
151

            
152
    private func deletePendingDevice() {
153
        if let device = pendingDeletion {
154
            _ = appData.deleteChargedDevice(id: device.id)
155
            pendingDeletion = nil
156
        }
157
    }
158
}