Files
andros 5bd8f3382a Apply theme colors directly in SetupView
Sheets run in a separate UIWindow and don't inherit preferredColorScheme from the parent. Applying background, toolbarBackground, preferredColorScheme and tint directly on SetupView so the settings screen updates immediately when a theme is selected.
2026-05-26 11:11:11 +02:00

176 lines
6.5 KiB
Swift

import SwiftUI
struct SetupView: View {
@Environment(WebDAVSettings.self) private var settings
@Environment(\.dismiss) private var dismiss
@State private var url = ""
@State private var username = ""
@State private var password = ""
@State private var notesPath = "/notes/"
@State private var testing = false
@State private var testResult: String?
@State private var testOK = false
@State private var themeManager = ThemeManager.shared
let isInitial: Bool
var body: some View {
NavigationStack {
Form {
Section("Server") {
TextField("URL", text: $url, prompt: Text("https://webdav.example.com"))
.textContentType(.URL)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.keyboardType(.URL)
TextField("Notes path", text: $notesPath)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
}
Section("Authentication") {
TextField("Username", text: $username)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
SecureField("Password", text: $password)
.textContentType(.password)
}
Section {
Button {
Task { await testConnection() }
} label: {
HStack {
Text("Test connection")
Spacer()
if testing { ProgressView() }
}
}
.disabled(testing || !canTest)
if let r = testResult {
Label(r, systemImage: testOK ? "checkmark.circle.fill" : "exclamationmark.triangle.fill")
.foregroundStyle(testOK ? .green : .red)
.font(.footnote)
}
}
Section {
Text("The password is stored securely in the iOS Keychain.")
.font(.footnote)
.foregroundStyle(.secondary)
}
appearanceSection
}
.scrollContentBackground(.hidden)
.background(themeManager.current.background)
.navigationTitle(isInitial ? "Connect" : "Settings")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(themeManager.current.secondaryBackground, for: .navigationBar)
.toolbar {
if !isInitial {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") { dismiss() }
}
}
ToolbarItem(placement: .confirmationAction) {
Button("Save") { save() }
.disabled(!canSave)
}
}
.onAppear(perform: load)
}
.preferredColorScheme(themeManager.current.colorScheme)
.tint(themeManager.current.accent)
}
private var canTest: Bool {
!url.trimmingCharacters(in: .whitespaces).isEmpty &&
!username.trimmingCharacters(in: .whitespaces).isEmpty
}
private var canSave: Bool {
canTest && !password.trimmingCharacters(in: .whitespaces).isEmpty
}
private func load() {
url = settings.url
username = settings.username
password = settings.password
notesPath = settings.notesPath
}
private func save() {
settings.url = url.trimmingCharacters(in: .whitespaces)
settings.username = username.trimmingCharacters(in: .whitespaces)
settings.password = password.trimmingCharacters(in: .whitespaces)
settings.notesPath = notesPath.trimmingCharacters(in: .whitespaces)
dismiss()
}
private var appearanceSection: some View {
Section {
ForEach(AppTheme.all) { theme in
HStack(spacing: 12) {
themeSwatches(theme)
Text(theme.name)
Spacer()
if themeManager.current.id == theme.id {
Image(systemName: "checkmark")
.foregroundStyle(Color.accentColor)
.fontWeight(.semibold)
}
}
.contentShape(Rectangle())
.onTapGesture { themeManager.select(theme) }
}
} header: {
Text("Appearance")
}
}
private func themeSwatches(_ theme: AppTheme) -> some View {
HStack(spacing: 2) {
RoundedRectangle(cornerRadius: 3)
.fill(theme.background)
.frame(width: 18, height: 28)
.overlay(RoundedRectangle(cornerRadius: 3).strokeBorder(Color.secondary.opacity(0.2), lineWidth: 0.5))
RoundedRectangle(cornerRadius: 3)
.fill(theme.secondaryBackground)
.frame(width: 18, height: 28)
RoundedRectangle(cornerRadius: 3)
.fill(theme.accent)
.frame(width: 18, height: 28)
}
.clipShape(RoundedRectangle(cornerRadius: 4))
.overlay(RoundedRectangle(cornerRadius: 4).strokeBorder(Color.secondary.opacity(0.15), lineWidth: 0.5))
}
@MainActor
private func testConnection() async {
testing = true
defer { testing = false }
testResult = nil
let probe = WebDAVConfig(
url: url.trimmingCharacters(in: .whitespaces),
username: username.trimmingCharacters(in: .whitespaces),
password: password.trimmingCharacters(in: .whitespaces),
notesPath: notesPath.trimmingCharacters(in: .whitespaces)
)
do {
let count = try await WebDAVClient.probe(config: probe)
testOK = true
testResult = "Connected. \(count) note\(count == 1 ? "" : "s") found."
} catch URLError.userAuthenticationRequired {
testOK = false
testResult = "Invalid credentials (401 Unauthorized)."
} catch {
testOK = false
testResult = error.localizedDescription
}
}
}
#Preview {
SetupView(isInitial: true)
.environment(WebDAVSettings())
}