Files
linkding-ios/Marks/Views/OnboardingView.swift
Krishna Kumar 64a944ab10
All checks were successful
CI / build-and-deploy (push) Successful in 14s
style(ui): adopt Liquid Glass on opaque controls
Swap hardcoded systemGray/Color.blue pill and button fills for the iOS 26
Liquid Glass APIs so controls read native under the new design system:
- BookmarkRow tag chips: glassEffect in a GlassEffectContainer
- Podcast speed button: glassEffect capsule
- Podcast retry + Onboarding Connect: glassProminent button style

Onboarding text-field fills left as systemGray6 (standard adaptive field
fill; glass behind editable text hurts legibility).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 13:10:23 -05:00

141 lines
5.1 KiB
Swift

import SwiftUI
struct OnboardingView: View {
let onConnect: (ServerConfig) -> Void
@State private var urlText = ""
@State private var token = ""
@State private var isConnecting = false
@State private var errorMessage: String?
@FocusState private var focused: Field?
enum Field { case url, token }
var body: some View {
VStack(spacing: 0) {
Spacer()
VStack(alignment: .leading, spacing: 8) {
Text("Marks")
.font(.system(size: 42, weight: .semibold, design: .rounded))
Text("Your bookmarks, beautifully.")
.font(.system(size: 17))
.foregroundStyle(.secondary)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 28)
.padding(.bottom, 48)
VStack(spacing: 14) {
VStack(alignment: .leading, spacing: 6) {
Text("Server URL")
.font(.system(size: 13, weight: .medium))
.foregroundStyle(.secondary)
.padding(.horizontal, 2)
TextField("https://links.example.com", text: $urlText)
.textContentType(.URL)
.keyboardType(.URL)
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
.focused($focused, equals: .url)
.submitLabel(.next)
.onSubmit { focused = .token }
.padding(14)
.background(Color(.systemGray6))
.clipShape(RoundedRectangle(cornerRadius: 12))
}
VStack(alignment: .leading, spacing: 6) {
Text("API Token")
.font(.system(size: 13, weight: .medium))
.foregroundStyle(.secondary)
.padding(.horizontal, 2)
SecureField("Paste your token", text: $token)
.textContentType(.password)
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
.focused($focused, equals: .token)
.submitLabel(.done)
.onSubmit { Task { await connect() } }
.padding(14)
.background(Color(.systemGray6))
.clipShape(RoundedRectangle(cornerRadius: 12))
Text("Find your token at Settings → API Token in Linkding.")
.font(.system(size: 12))
.foregroundStyle(.tertiary)
.padding(.horizontal, 2)
}
}
.padding(.horizontal, 24)
if let err = errorMessage {
Text(err)
.font(.system(size: 14))
.foregroundStyle(.red)
.padding(.top, 12)
.padding(.horizontal, 28)
}
Button {
Task { await connect() }
} label: {
Group {
if isConnecting {
ProgressView().tint(.white)
} else {
Text("Connect")
.font(.system(size: 17, weight: .semibold))
}
}
.frame(maxWidth: .infinity)
.frame(height: 52)
}
.buttonStyle(.glassProminent)
.buttonBorderShape(.roundedRectangle(radius: 14))
.disabled(!canConnect || isConnecting)
.padding(.horizontal, 24)
.padding(.top, 24)
Spacer()
}
}
private var canConnect: Bool { !urlText.isEmpty && !token.isEmpty }
private func connect() async {
focused = nil
isConnecting = true
errorMessage = nil
do {
let config = try parseConfig()
let api = LinkdingAPI(config: config)
try await api.verifyConnection()
onConnect(config)
} catch APIError.badStatus(401) {
errorMessage = "Invalid token. Check your API token in Linkding settings."
} catch APIError.badStatus(let code) {
errorMessage = "Server returned \(code). Check the URL."
} catch {
errorMessage = "Could not connect. Check the URL and try again."
}
isConnecting = false
}
private func parseConfig() throws -> ServerConfig {
let raw = urlText.normalizedURL
guard let comps = URLComponents(string: raw),
let host = comps.host, !host.isEmpty else {
throw URLError(.badURL)
}
return ServerConfig(
host: host,
port: comps.port,
path: comps.path.hasSuffix("/") ? String(comps.path.dropLast()) : comps.path,
token: token.trimmingCharacters(in: .whitespacesAndNewlines),
useHttps: comps.scheme == "https"
)
}
}