Files
jarvis/Jarvis/Views/Onboarding/OnboardingView.swift
Robin Kutesa 27d0e22767 Initial commit: Jarvis iOS app
SwiftUI client for a self-hosted RSS news-correlation platform: signal feed,
story detail, article reader, feed manager, and LAN⇄Tailscale connectivity.
Project generated from project.yml via XcodeGen.

Includes CI build matrix (macOS 14/15 × Debug/Release), issue templates,
backlog, and API/backend handoff docs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 18:04:59 +03:00

129 lines
4.8 KiB
Swift

// OnboardingView.swift
import SwiftUI
struct OnboardingView: View {
@EnvironmentObject var settings: ServerSettings
@EnvironmentObject var connectivity: ConnectivitySettings
@State private var host = ""
@State private var isConnecting = false
@State private var errorMessage: String?
var body: some View {
ZStack {
Color.black.ignoresSafeArea()
VStack(alignment: .leading, spacing: 0) {
Spacer().frame(height: 48)
// Wordmark
Group {
Text("J").foregroundColor(Color("KisaniOrange")) +
Text("arvis").foregroundColor(.white)
}
.font(.system(size: 48, weight: .black, design: .default))
.kerning(-2)
Text("Point Jarvis at your platform\nto start receiving signals.")
.font(.system(size: 16))
.foregroundColor(Color(.systemGray))
.lineSpacing(4)
.padding(.top, 10)
.padding(.bottom, 40)
// Field label
Text("Server address")
.font(.system(size: 11, weight: .bold))
.foregroundColor(Color(.systemGray2))
.kerning(0.8)
.textCase(.uppercase)
.padding(.bottom, 8)
// Input field
HStack(spacing: 12) {
Image(systemName: "server.rack")
.font(.system(size: 18))
.foregroundColor(host.isEmpty ? Color(.systemGray3) : Color("KisaniOrange"))
TextField("192.168.1.42:8080", text: $host)
.font(.system(size: 15, design: .monospaced))
.foregroundColor(Color("KisaniOrange"))
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
.keyboardType(.URL)
}
.padding(14)
.background(host.isEmpty ? Color(.systemGray6).opacity(0.1) : Color("KisaniOrange").opacity(0.07))
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(host.isEmpty ? Color(.systemGray5) : Color("KisaniOrange"), lineWidth: 0.5)
)
.cornerRadius(12)
Text("http:// or https:// · local network or VPN")
.font(.system(size: 11, design: .monospaced))
.foregroundColor(Color(.systemGray4))
.padding(.top, 8)
.padding(.bottom, 32)
if let errorMessage {
Text(errorMessage)
.font(.system(size: 13))
.foregroundColor(.red.opacity(0.8))
.padding(.bottom, 12)
}
// Connect button
Button {
Task { await connect() }
} label: {
HStack {
Spacer()
if isConnecting {
ProgressView().tint(.white)
} else {
Text("Connect to Jarvis")
.font(.system(size: 16, weight: .bold))
.foregroundColor(.white)
}
Spacer()
}
.frame(height: 52)
.background(host.isEmpty ? Color(.systemGray5) : Color("KisaniOrange"))
.cornerRadius(14)
}
.disabled(host.isEmpty || isConnecting)
Button("Scan QR code instead") {}
.font(.system(size: 14))
.foregroundColor(Color(.systemGray3))
.frame(maxWidth: .infinity)
.frame(height: 48)
Spacer()
Text("Self-hosted · no account needed")
.font(.system(size: 11, design: .monospaced))
.foregroundColor(Color(.systemGray5))
.frame(maxWidth: .infinity, alignment: .center)
.padding(.bottom, 24)
}
.padding(.horizontal, 24)
}
}
private func connect() async {
isConnecting = true
errorMessage = nil
do {
try await settings.save(host: host)
// Seed the Direct/LAN endpoint so connectivity can fail over to a VPN later.
if connectivity.directHost.trimmingCharacters(in: .whitespaces).isEmpty {
connectivity.directHost = host.trimmingCharacters(in: .whitespaces)
}
} catch {
errorMessage = error.localizedDescription
}
isConnecting = false
}
}