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