Multi-Server implementiert

This commit is contained in:
2026-03-22 11:04:52 +01:00
parent 6b3b2db013
commit c4a4833bec
12 changed files with 603 additions and 49 deletions
+110 -32
View File
@@ -1,5 +1,6 @@
import SwiftUI
import SafariServices
import SwiftData
struct SettingsView: View {
@AppStorage("onboardingComplete") private var onboardingComplete = false
@@ -8,11 +9,11 @@ struct SettingsView: View {
@AppStorage("appTheme") private var appTheme = "system"
@AppStorage("accentTheme") private var accentThemeRaw = AccentTheme.ocean.rawValue
@AppStorage("loggingEnabled") private var loggingEnabled = false
@Environment(ServerProfileStore.self) private var profileStore
private var selectedTheme: AccentTheme {
AccentTheme(rawValue: accentThemeRaw) ?? .ocean
}
@State private var serverURL = UserDefaults.standard.string(forKey: "serverURL") ?? ""
@State private var showSignOutAlert = false
@State private var isSyncing = false
@State private var lastSynced = UserDefaults.standard.object(forKey: "lastSynced") as? Date
@@ -20,6 +21,11 @@ struct SettingsView: View {
@State private var selectedLanguage: LanguageManager.Language = LanguageManager.shared.current
@State private var showLogViewer = false
@State private var shareItems: [Any]? = nil
@Environment(\.modelContext) private var modelContext
@State private var showAddServer = false
@State private var profileToSwitch: ServerProfile? = nil
@State private var profileToDelete: ServerProfile? = nil
@State private var profileToEdit: ServerProfile? = nil
private let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0"
private let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "1"
@@ -93,33 +99,56 @@ struct SettingsView: View {
.padding(.vertical, 4)
}
// Account section
Section(L("settings.account")) {
HStack {
Image(systemName: "person.circle.fill")
.font(.title)
.foregroundStyle(.blue)
VStack(alignment: .leading) {
Text(L("settings.account.connected"))
.font(.headline)
Text(serverURL)
.font(.footnote)
.foregroundStyle(.secondary)
.lineLimit(1)
// Servers section
Section(L("settings.servers")) {
ForEach(profileStore.profiles) { profile in
Button {
if profile.id != profileStore.activeProfileId {
profileToSwitch = profile
}
} label: {
HStack(spacing: 12) {
Image(systemName: profile.id == profileStore.activeProfileId
? "checkmark.circle.fill" : "circle")
.foregroundStyle(profile.id == profileStore.activeProfileId
? Color.accentColor : .secondary)
VStack(alignment: .leading, spacing: 2) {
Text(profile.name)
.font(.body)
.foregroundStyle(.primary)
Text(profile.serverURL)
.font(.footnote)
.foregroundStyle(.secondary)
.lineLimit(1)
}
Spacer()
if profile.id == profileStore.activeProfileId {
Text(L("settings.servers.active"))
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
Button(role: .destructive) {
profileToDelete = profile
} label: {
Label(L("settings.servers.delete.confirm"), systemImage: "trash")
}
.tint(.red)
Button {
profileToEdit = profile
} label: {
Label(L("settings.servers.edit"), systemImage: "pencil")
}
.tint(.blue)
}
}
.padding(.vertical, 4)
Button {
UIPasteboard.general.string = serverURL
showAddServer = true
} label: {
Label(L("settings.account.copyurl"), systemImage: "doc.on.doc")
}
Button(role: .destructive) {
showSignOutAlert = true
} label: {
Label(L("settings.account.signout"), systemImage: "rectangle.portrait.and.arrow.right")
Label(L("settings.servers.add"), systemImage: "plus.circle")
}
}
@@ -207,11 +236,58 @@ struct SettingsView: View {
.onAppear {
loggingEnabled = LogManager.shared.isEnabled
}
.alert(L("settings.signout.alert.title"), isPresented: $showSignOutAlert) {
Button(L("settings.signout.alert.confirm"), role: .destructive) { signOut() }
Button(L("settings.signout.alert.cancel"), role: .cancel) {}
// Switch server confirmation
.alert(L("settings.servers.switch.title"), isPresented: Binding(
get: { profileToSwitch != nil },
set: { if !$0 { profileToSwitch = nil } }
)) {
Button(L("settings.servers.switch.confirm")) {
if let p = profileToSwitch { profileStore.activate(p) }
profileToSwitch = nil
}
Button(L("settings.signout.alert.cancel"), role: .cancel) { profileToSwitch = nil }
} message: {
Text(L("settings.signout.alert.message"))
if let p = profileToSwitch {
Text(String(format: L("settings.servers.switch.message"), p.name))
}
}
// Delete inactive server confirmation
.alert(L("settings.servers.delete.title"), isPresented: Binding(
get: { profileToDelete != nil && profileToDelete?.id != profileStore.activeProfileId },
set: { if !$0 { profileToDelete = nil } }
)) {
Button(L("settings.servers.delete.confirm"), role: .destructive) {
if let p = profileToDelete { removeProfile(p) }
profileToDelete = nil
}
Button(L("settings.signout.alert.cancel"), role: .cancel) { profileToDelete = nil }
} message: {
if let p = profileToDelete {
Text(String(format: L("settings.servers.delete.message"), p.name))
}
}
// Delete ACTIVE server stronger warning
.alert(L("settings.servers.delete.active.title"), isPresented: Binding(
get: { profileToDelete != nil && profileToDelete?.id == profileStore.activeProfileId },
set: { if !$0 { profileToDelete = nil } }
)) {
Button(L("settings.servers.delete.confirm"), role: .destructive) {
if let p = profileToDelete { removeProfile(p) }
profileToDelete = nil
}
Button(L("settings.signout.alert.cancel"), role: .cancel) { profileToDelete = nil }
} message: {
if let p = profileToDelete {
Text(String(format: L("settings.servers.delete.active.message"), p.name))
}
}
// Add server sheet
.sheet(isPresented: $showAddServer) {
AddServerView()
}
// Edit server sheet
.sheet(item: $profileToEdit) { profile in
EditServerView(profile: profile)
}
.sheet(item: $showSafari) { url in
SafariView(url: url)
@@ -233,11 +309,13 @@ struct SettingsView: View {
// MARK: - Actions
private func signOut() {
Task {
try? await KeychainService.shared.deleteCredentials()
UserDefaults.standard.removeObject(forKey: "serverURL")
UserDefaults.standard.removeObject(forKey: "lastSynced")
private func removeProfile(_ profile: ServerProfile) {
profileStore.remove(profile)
// Always clear the cache it may contain content from this server
try? SyncService.shared.clearAllCache(context: modelContext)
lastSynced = nil
// If no profiles remain, return to onboarding
if profileStore.profiles.isEmpty {
onboardingComplete = false
}
}