- Kassenzettel-Kachel vom Listen- in den Scannen-Tab verschoben (passt thematisch). - App-Akzent auf Markenblau (#3F51B5/#7C8AE8, wie Web/App-Icon) via .tint; alle Color.accentColor -> Color.marke. System-Blau verschwindet. - Kachel-Buttons/Links auf .buttonStyle(.plain): weisse Schrift/Symbole statt getoentem Blau. - Kassenzettel-Pruefen-Liste: je Artikel eine eigene Section (insetGrouped) mit Artikel/Menge/Lagerort in eigenen Zeilen - klar gruppiert, nicht mehr gequetscht. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
212 lines
8.3 KiB
Swift
212 lines
8.3 KiB
Swift
import SwiftUI
|
||
|
||
/// Wechselt den Server aus der Werkzeugleiste heraus - der schnelle Weg.
|
||
/// Die vollstaendige Verwaltung liegt in `ServerListView`.
|
||
struct ServerMenu: View {
|
||
@EnvironmentObject private var session: Session
|
||
@Binding var manageShown: Bool
|
||
|
||
var body: some View {
|
||
Menu {
|
||
if session.profiles.count > 1 {
|
||
Section("Server") {
|
||
ForEach(session.profiles) { profile in
|
||
Button {
|
||
guard profile.id != session.activeProfileID else { return }
|
||
session.switchTo(profile.id)
|
||
} label: {
|
||
// Der Haken zeigt, wo man gerade ist. Ein leerer
|
||
// Symbolname wuerde eine kaputte Lucke hinterlassen,
|
||
// deshalb zwei getrennte Beschriftungen.
|
||
if profile.id == session.activeProfileID {
|
||
Label(profile.name, systemImage: "checkmark")
|
||
} else {
|
||
Text(profile.name)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
Button("Server verwalten…", systemImage: "server.rack") { manageShown = true }
|
||
if session.isLoggedIn {
|
||
Divider()
|
||
Text("Angemeldet als \(session.username)")
|
||
Button("Abmelden", role: .destructive) { session.logout() }
|
||
}
|
||
} label: {
|
||
Image(systemName: "person.circle")
|
||
}
|
||
}
|
||
}
|
||
|
||
struct ServerListView: View {
|
||
@EnvironmentObject private var session: Session
|
||
@Environment(\.dismiss) private var dismiss
|
||
|
||
@State private var editing: ServerProfile?
|
||
@State private var addShown = false
|
||
@State private var pendingDeletion: ServerProfile?
|
||
@State private var notifyFor: ServerProfile?
|
||
|
||
var body: some View {
|
||
NavigationStack {
|
||
List {
|
||
Section {
|
||
ForEach(session.profiles) { profile in
|
||
HStack(spacing: 8) {
|
||
// Zeile wechselt den Server, die Glocke oeffnet die
|
||
// Benachrichtigungen - zwei getrennte Ziele in einer Zeile.
|
||
Button {
|
||
session.switchTo(profile.id)
|
||
dismiss()
|
||
} label: {
|
||
row(for: profile)
|
||
}
|
||
.buttonStyle(.plain)
|
||
Button {
|
||
notifyFor = profile
|
||
} label: {
|
||
Image(systemName: NotificationStore.config(for: profile.id).enabled
|
||
? "bell.fill" : "bell")
|
||
.foregroundStyle(Color.marke)
|
||
}
|
||
.buttonStyle(.borderless)
|
||
}
|
||
.swipeActions(edge: .trailing) {
|
||
Button("Löschen", role: .destructive) { pendingDeletion = profile }
|
||
Button("Bearbeiten") { editing = profile }.tint(.gray)
|
||
}
|
||
}
|
||
} footer: {
|
||
Text("Jeder Server merkt sich seine eigene Anmeldung. Ein Wechsel meldet dich nicht ab. Die Glocke stellt Ablauf-Erinnerungen ein.")
|
||
}
|
||
|
||
if session.profiles.isEmpty {
|
||
Text("Noch kein Server hinterlegt.")
|
||
.foregroundStyle(.secondary)
|
||
}
|
||
}
|
||
.navigationTitle("Server")
|
||
.navigationBarTitleDisplayMode(.inline)
|
||
.toolbar {
|
||
ToolbarItem(placement: .topBarTrailing) {
|
||
Button("Hinzufügen", systemImage: "plus") { addShown = true }
|
||
}
|
||
ToolbarItem(placement: .topBarLeading) {
|
||
Button("Fertig") { dismiss() }
|
||
}
|
||
}
|
||
.sheet(isPresented: $addShown) {
|
||
ServerEditView(profile: nil)
|
||
}
|
||
.sheet(item: $editing) { profile in
|
||
ServerEditView(profile: profile)
|
||
}
|
||
.sheet(item: $notifyFor) { profile in
|
||
NavigationStack {
|
||
NotificationSettingsView(profileID: profile.id, serverName: profile.name)
|
||
.toolbar {
|
||
ToolbarItem(placement: .topBarLeading) {
|
||
Button("Fertig") { notifyFor = nil }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.confirmationDialog(
|
||
pendingDeletion.map { "„\($0.name)“ löschen?" } ?? "",
|
||
isPresented: Binding(get: { pendingDeletion != nil },
|
||
set: { if !$0 { pendingDeletion = nil } }),
|
||
titleVisibility: .visible
|
||
) {
|
||
Button("Löschen", role: .destructive) {
|
||
if let profile = pendingDeletion { session.removeProfile(id: profile.id) }
|
||
pendingDeletion = nil
|
||
}
|
||
Button("Abbrechen", role: .cancel) { pendingDeletion = nil }
|
||
} message: {
|
||
Text("Die gespeicherte Anmeldung für diesen Server wird mit entfernt. Auf dem Server selbst ändert sich nichts.")
|
||
}
|
||
}
|
||
}
|
||
|
||
private func row(for profile: ServerProfile) -> some View {
|
||
HStack {
|
||
VStack(alignment: .leading, spacing: 2) {
|
||
Text(profile.name).font(.body)
|
||
Text(profile.urlString).font(.caption).foregroundStyle(.secondary)
|
||
.lineLimit(1).truncationMode(.middle)
|
||
}
|
||
Spacer()
|
||
if profile.id == session.activeProfileID {
|
||
Image(systemName: "checkmark").foregroundStyle(Color.marke)
|
||
}
|
||
}
|
||
.foregroundStyle(.primary)
|
||
}
|
||
}
|
||
|
||
/// Anlegen und Bearbeiten in einem - der Unterschied ist nur, ob schon ein
|
||
/// Profil hereingereicht wurde.
|
||
struct ServerEditView: View {
|
||
@EnvironmentObject private var session: Session
|
||
@Environment(\.dismiss) private var dismiss
|
||
|
||
let profile: ServerProfile?
|
||
|
||
@State private var name: String
|
||
@State private var urlString: String
|
||
|
||
init(profile: ServerProfile?) {
|
||
self.profile = profile
|
||
_name = State(initialValue: profile?.name ?? "")
|
||
_urlString = State(initialValue: profile?.urlString ?? "")
|
||
}
|
||
|
||
private var canSave: Bool {
|
||
Session.normalize(urlString) != nil
|
||
}
|
||
|
||
var body: some View {
|
||
NavigationStack {
|
||
Form {
|
||
// Der Name steht bewusst oben: Er ist das, was in der App zu
|
||
// sehen ist ("Zuhause"), die Adresse braucht man nur einmal.
|
||
Section {
|
||
TextField("z. B. Zuhause", text: $name)
|
||
} header: {
|
||
Text("Name")
|
||
} footer: {
|
||
Text("So heißt der Server in der App. Ohne Angabe wird der Rechnername verwendet – meist die IP-Adresse.")
|
||
}
|
||
Section("Adresse") {
|
||
TextField("http://192.168.1.10:8080", text: $urlString)
|
||
.textInputAutocapitalization(.never)
|
||
.autocorrectionDisabled()
|
||
.keyboardType(.URL)
|
||
}
|
||
}
|
||
.navigationTitle(profile == nil ? "Server hinzufügen" : "Server bearbeiten")
|
||
.navigationBarTitleDisplayMode(.inline)
|
||
.toolbar {
|
||
ToolbarItem(placement: .topBarLeading) {
|
||
Button("Abbrechen") { dismiss() }
|
||
}
|
||
ToolbarItem(placement: .topBarTrailing) {
|
||
Button("Sichern") { save() }.disabled(!canSave)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private func save() {
|
||
if let profile {
|
||
session.updateProfile(id: profile.id, name: name, urlString: urlString)
|
||
} else {
|
||
// Nach dem Anlegen ist der neue Server der aktive; fehlt dort eine
|
||
// Anmeldung, erscheint sie von selbst.
|
||
session.addProfile(name: name, urlString: urlString)
|
||
}
|
||
dismiss()
|
||
}
|
||
}
|