Files
Camper-Monitor/CamperMonitor/Views/ProfilesView.swift
T
2026-08-30 10:36:52 +02:00

169 lines
6.2 KiB
Swift

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
}
}
}
}