USB-Meter / USB Meter / Views / Sidebar / SidebarChargedDeviceLibraryView.swift
Newer Older
159 lines | 5.207kb
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
        }
Bogdan Timofte authored a month ago
41
        .sidebarToggleToolbarItem()
Bogdan Timofte authored a month ago
42
        .sheet(isPresented: $showingNewEditor) { newEditorSheet }
43
        .sheet(item: $editingChargedDevice) { device in editEditorSheet(device) }
44
        .confirmationDialog(
45
            "Delete \(pendingDeletion?.name ?? mode.singularTitle)?",
46
            isPresented: Binding(
47
                get: { pendingDeletion != nil },
48
                set: { if !$0 { pendingDeletion = nil } }
49
            ),
50
            titleVisibility: .visible
51
        ) {
52
            Button("Delete", role: .destructive, action: deletePendingDevice)
53
            Button("Cancel", role: .cancel) { pendingDeletion = nil }
54
        } message: {
55
            Text("This will permanently remove the \(mode.singularTitle.lowercased()) and all associated data.")
56
        }
57
    }
58

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

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

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

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

            
110
    @ViewBuilder
111
    private var newEditorSheet: some View {
112
        if mode == .charger {
Bogdan Timofte authored a month ago
113
            ChargerEditorSheetView()
Bogdan Timofte authored a month ago
114
                .environmentObject(appData)
115
        } else {
Bogdan Timofte authored a month ago
116
            ChargedDeviceEditorSheetView()
Bogdan Timofte authored a month ago
117
                .environmentObject(appData)
118
        }
119
    }
120

            
121
    @ViewBuilder
122
    private func editEditorSheet(_ device: ChargedDeviceSummary) -> some View {
123
        if device.isCharger {
124
            ChargerEditorSheetView(chargedDevice: device)
125
                .environmentObject(appData)
126
        } else {
Bogdan Timofte authored a month ago
127
            ChargedDeviceEditorSheetView(chargedDevice: device)
Bogdan Timofte authored a month ago
128
                .environmentObject(appData)
129
        }
130
    }
131

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

            
147
    private var emptyStateDescription: String {
148
        mode == .device
Bogdan Timofte authored a month ago
149
            ? "Create one here, then select it explicitly when starting a charging session."
150
            : "Create one here, then select it explicitly for wireless charging sessions or standby measurements."
Bogdan Timofte authored a month ago
151
    }
152

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