forked from fritob/Camper-Monitor
init
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
import SwiftUI
|
||||
|
||||
/// Fahrzeuge anlegen, umbenennen und entfernen.
|
||||
struct ProfilesView: View {
|
||||
@Environment(DeviceStore.self) private var store
|
||||
@Environment(BluetoothManager.self) private var bluetooth
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@State private var editing: Profile?
|
||||
@State private var pendingDeletion: Profile?
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
Section {
|
||||
ForEach(store.profiles) { profile in
|
||||
row(for: profile)
|
||||
}
|
||||
} footer: {
|
||||
Text("Jedes Fahrzeug hat seine eigenen Geräte. Die App liest immer "
|
||||
+ "nur die des gewählten Fahrzeugs aus.")
|
||||
}
|
||||
|
||||
Section {
|
||||
Button("Fahrzeug hinzufügen", systemImage: "plus") {
|
||||
let profile = store.addProfile(named: "Camper \(store.profiles.count + 1)")
|
||||
editing = profile
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Fahrzeuge")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button("Fertig") { dismiss() }
|
||||
}
|
||||
}
|
||||
.sheet(item: $editing) { profile in
|
||||
ProfileEditView(profile: profile)
|
||||
}
|
||||
.confirmationDialog("Fahrzeug entfernen?",
|
||||
isPresented: .init(get: { pendingDeletion != nil },
|
||||
set: { if !$0 { pendingDeletion = nil } }),
|
||||
titleVisibility: .visible) {
|
||||
Button("Entfernen", role: .destructive) {
|
||||
if let pendingDeletion {
|
||||
store.removeProfile(pendingDeletion)
|
||||
bluetooth.refreshConfiguration()
|
||||
}
|
||||
pendingDeletion = nil
|
||||
}
|
||||
} message: {
|
||||
if let pendingDeletion {
|
||||
Text("„\(pendingDeletion.name)“ und die \(store.deviceCount(in: pendingDeletion)) "
|
||||
+ "darin eingerichteten Geräte werden gelöscht.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func row(for profile: Profile) -> some View {
|
||||
HStack {
|
||||
Label {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(profile.name)
|
||||
Text(deviceSummary(for: profile))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
} icon: {
|
||||
Image(systemName: profile.symbol)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if profile.id == store.activeProfileID {
|
||||
Image(systemName: "checkmark")
|
||||
.foregroundStyle(.tint)
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
}
|
||||
.contentShape(.rect)
|
||||
.onTapGesture {
|
||||
store.selectProfile(profile)
|
||||
bluetooth.refreshConfiguration()
|
||||
}
|
||||
.swipeActions(edge: .trailing) {
|
||||
// Das letzte Fahrzeug muss bleiben, sonst hätten Geräte keinen Ort.
|
||||
if store.hasMultipleProfiles {
|
||||
Button("Entfernen", systemImage: "trash", role: .destructive) {
|
||||
pendingDeletion = profile
|
||||
}
|
||||
}
|
||||
Button("Bearbeiten", systemImage: "pencil") {
|
||||
editing = profile
|
||||
}
|
||||
.tint(.gray)
|
||||
}
|
||||
}
|
||||
|
||||
private func deviceSummary(for profile: Profile) -> String {
|
||||
let count = store.deviceCount(in: profile)
|
||||
return count == 1 ? "1 Gerät" : "\(count) Geräte"
|
||||
}
|
||||
}
|
||||
|
||||
/// Name und Symbol eines Fahrzeugs ändern.
|
||||
private struct ProfileEditView: View {
|
||||
let profile: Profile
|
||||
|
||||
@Environment(DeviceStore.self) private var store
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@State private var name = ""
|
||||
@State private var symbol = "box.truck"
|
||||
|
||||
private let columns = [GridItem(.adaptive(minimum: 60), spacing: 12)]
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
Section("Name") {
|
||||
TextField("Name", text: $name)
|
||||
}
|
||||
Section("Symbol") {
|
||||
LazyVGrid(columns: columns, spacing: 12) {
|
||||
ForEach(Profile.symbols, id: \.self) { candidate in
|
||||
Button {
|
||||
symbol = candidate
|
||||
} label: {
|
||||
Image(systemName: candidate)
|
||||
.font(.title2)
|
||||
.frame(width: 52, height: 52)
|
||||
.background(symbol == candidate ? Color.accentColor.opacity(0.18)
|
||||
: Color.secondary.opacity(0.08),
|
||||
in: .rect(cornerRadius: 12))
|
||||
.foregroundStyle(symbol == candidate ? Color.accentColor : Color.primary)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Fahrzeug")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("Abbrechen") { dismiss() }
|
||||
}
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button("Sichern") {
|
||||
var updated = profile
|
||||
updated.name = name.trimmingCharacters(in: .whitespaces)
|
||||
updated.symbol = symbol
|
||||
store.update(updated)
|
||||
dismiss()
|
||||
}
|
||||
.disabled(name.trimmingCharacters(in: .whitespaces).isEmpty)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
name = profile.name
|
||||
symbol = profile.symbol
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user