USB-Meter / USB Meter / Views / ChargedDevices / Sheets / Library / ChargedDeviceLibrarySheetView.swift
Newer Older
201 lines | 6.59kb
Bogdan Timofte authored a month ago
1
//
2
//  ChargedDeviceLibrarySheetView.swift
3
//  USB Meter
4
//
5
//  Created by Codex on 10/04/2026.
6
//
7

            
8
import SwiftUI
9

            
10
enum ChargedDeviceLibraryMode {
11
    case device
12
    case charger
13

            
14
    var kind: ChargedDeviceKind {
15
        switch self {
16
        case .device:
17
            return .device
18
        case .charger:
19
            return .charger
20
        }
21
    }
22

            
23
    var title: String {
24
        kind.pluralTitle
25
    }
26

            
27
    var singularTitle: String {
28
        kind.title
29
    }
30
}
31

            
32
struct ChargedDeviceLibrarySheetView: View {
33
    @EnvironmentObject private var appData: AppData
34
    @Environment(\.dismiss) private var dismiss
35

            
36
    let meterTint: Color
37
    let mode: ChargedDeviceLibraryMode
38
    /// true = standalone sheet with own NavigationView; false = pushed into parent nav stack
39
    let standalone: Bool
40

            
41
    @State private var showingNewEditor = false
42
    @State private var editingChargedDevice: ChargedDeviceSummary?
43
    @State private var pendingDeletion: ChargedDeviceSummary?
44

            
45
    init(
46
        meterTint: Color,
47
        mode: ChargedDeviceLibraryMode,
48
        standalone: Bool = true
49
    ) {
50
        self.meterTint = meterTint
51
        self.mode = mode
52
        self.standalone = standalone
53
    }
54

            
55
    var body: some View {
56
        if standalone {
57
            NavigationView { listContent }
58
                .navigationViewStyle(StackNavigationViewStyle())
59
        } else {
60
            listContent
61
        }
62
    }
63

            
64
    private var listContent: some View {
65
        List {
66
            if displayedChargedDevices.isEmpty {
67
                VStack(alignment: .leading, spacing: 10) {
68
                    HStack(spacing: 8) {
69
                        Text("No \(mode.title.lowercased()) yet.")
70
                            .font(.headline)
71
                        ContextInfoButton(
72
                            title: mode.title,
73
                            message: emptyStateDescription
74
                        )
75
                    }
76
                }
77
                .padding(.vertical, 10)
78
                .listRowBackground(Color.clear)
79
            } else {
80
                ForEach(displayedChargedDevices) { chargedDevice in
Bogdan Timofte authored a month ago
81
                    NavigationLink(destination: ChargedDeviceSettingsView(chargedDeviceID: chargedDevice.id)) {
Bogdan Timofte authored a month ago
82
                        ChargedDeviceLibraryRowView(
83
                            chargedDevice: chargedDevice,
Bogdan Timofte authored a month ago
84
                            isSelected: false
Bogdan Timofte authored a month ago
85
                        )
86
                    }
87
                    .swipeActions(edge: .trailing, allowsFullSwipe: false) {
88
                        Button(role: .destructive) {
89
                            pendingDeletion = chargedDevice
90
                        } label: {
91
                            Label("Delete", systemImage: "trash")
92
                        }
93
                        Button {
94
                            editingChargedDevice = chargedDevice
95
                        } label: {
96
                            Label("Edit", systemImage: "pencil")
97
                        }
98
                        .tint(.blue)
99
                    }
100
                    .contextMenu {
101
                        Button {
102
                            editingChargedDevice = chargedDevice
103
                        } label: {
104
                            Label("Edit \(mode.singularTitle)", systemImage: "pencil")
105
                        }
106
                        Button(role: .destructive) {
107
                            pendingDeletion = chargedDevice
108
                        } label: {
109
                            Label("Delete \(mode.singularTitle)", systemImage: "trash")
110
                        }
111
                    }
112
                }
113
            }
114
        }
115
        .listStyle(InsetGroupedListStyle())
116
        .background(
117
            LinearGradient(
118
                colors: [meterTint.opacity(0.14), Color.clear],
119
                startPoint: .topLeading,
120
                endPoint: .bottomTrailing
121
            )
122
            .ignoresSafeArea()
123
        )
124
        .navigationTitle(mode.title)
125
        .navigationBarTitleDisplayMode(.inline)
126
        .toolbar {
127
            ToolbarItem(placement: .cancellationAction) {
128
                if standalone {
129
                    Button("Done") { dismiss() }
130
                }
131
            }
132
            ToolbarItem(placement: .confirmationAction) {
133
                Button("New") { showingNewEditor = true }
134
            }
135
        }
136
        .sheet(isPresented: $showingNewEditor) {
137
            newEditorSheet
138
        }
139
        .sheet(item: $editingChargedDevice) { device in
140
            editEditorSheet(device)
141
        }
142
        .confirmationDialog(
143
            "Delete \(pendingDeletion?.name ?? mode.singularTitle)?",
144
            isPresented: Binding(
145
                get: { pendingDeletion != nil },
146
                set: { if !$0 { pendingDeletion = nil } }
147
            ),
148
            titleVisibility: .visible
149
        ) {
150
            Button("Delete", role: .destructive) {
151
                if let device = pendingDeletion {
152
                    _ = appData.deleteChargedDevice(id: device.id)
153
                    pendingDeletion = nil
154
                }
155
            }
156
            Button("Cancel", role: .cancel) { pendingDeletion = nil }
157
        } message: {
158
            Text("This will permanently remove the \(mode.singularTitle.lowercased()) and all associated data.")
159
        }
160
    }
161

            
162
    @ViewBuilder
163
    private var newEditorSheet: some View {
164
        if mode == .charger {
Bogdan Timofte authored a month ago
165
            ChargerEditorSheetView()
Bogdan Timofte authored a month ago
166
                .environmentObject(appData)
167
        } else {
Bogdan Timofte authored a month ago
168
            ChargedDeviceEditorSheetView()
Bogdan Timofte authored a month ago
169
                .environmentObject(appData)
170
        }
171
    }
172

            
173
    @ViewBuilder
174
    private func editEditorSheet(_ chargedDevice: ChargedDeviceSummary) -> some View {
175
        if chargedDevice.isCharger {
176
            ChargerEditorSheetView(chargedDevice: chargedDevice)
177
                .environmentObject(appData)
178
        } else {
Bogdan Timofte authored a month ago
179
            ChargedDeviceEditorSheetView(chargedDevice: chargedDevice)
Bogdan Timofte authored a month ago
180
            .environmentObject(appData)
181
        }
182
    }
183

            
184
    private var displayedChargedDevices: [ChargedDeviceSummary] {
185
        switch mode {
186
        case .device:
187
            return appData.deviceSummaries
188
        case .charger:
189
            return appData.chargerSummaries
190
        }
191
    }
192

            
193
    private var emptyStateDescription: String {
194
        switch mode {
195
        case .device:
Bogdan Timofte authored a month ago
196
            return "Create one here, then select it explicitly when starting a charging session."
Bogdan Timofte authored a month ago
197
        case .charger:
Bogdan Timofte authored a month ago
198
            return "Create one here, then select it explicitly for wireless charging sessions or standby measurements."
Bogdan Timofte authored a month ago
199
        }
200
    }
201
}