5bd8f3382a
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.
176 lines
6.5 KiB
Swift
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())
|
|
}
|