Files
jarvis/Jarvis/Connectivity/ConnectivitySettings.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

74 lines
3.0 KiB
Swift

// ConnectivitySettings.swift
// Jarvis persisted connection profiles: a Direct/LAN address and a Remote/VPN
// address for the same server, the chosen provider, and the auto-switch policy.
import Foundation
@MainActor
final class ConnectivitySettings: ObservableObject {
static let shared = ConnectivitySettings()
private let key = "jarvis_connectivity_v2"
@Published var provider: VPNProvider { didSet { persist() } }
@Published var directHost: String { didSet { persist() } } // LAN, e.g. 192.168.30.50:8080
@Published var remoteHost: String { didSet { persist() } } // Tailscale/VPN address
/// "Use <provider> when remote" when off, only the LAN address is ever used.
@Published var remoteEnabled: Bool { didSet { persist() } }
/// "Auto-connect" re-resolve automatically when the network path changes.
@Published var autoConnect: Bool { didSet { persist() } }
/// Debounce before switching endpoints after a network change (seconds).
@Published var switchDelaySeconds: Int { didSet { persist() } }
private struct Stored: Codable {
var provider: VPNProvider
var directHost: String
var remoteHost: String
var remoteEnabled: Bool?
var autoConnect: Bool?
var switchDelaySeconds: Int?
}
private init() {
if let data = UserDefaults.standard.data(forKey: key),
let s = try? JSONDecoder().decode(Stored.self, from: data) {
provider = s.provider
directHost = s.directHost
remoteHost = s.remoteHost
remoteEnabled = s.remoteEnabled ?? true
autoConnect = s.autoConnect ?? true
switchDelaySeconds = s.switchDelaySeconds ?? 5
} else {
provider = .tailscale
directHost = UserDefaults.standard.string(forKey: "jarvis_server_host") ?? ""
remoteHost = ""
remoteEnabled = true
autoConnect = true
switchDelaySeconds = 5
}
}
private func persist() {
let s = Stored(provider: provider, directHost: directHost, remoteHost: remoteHost,
remoteEnabled: remoteEnabled, autoConnect: autoConnect,
switchDelaySeconds: switchDelaySeconds)
if let data = try? JSONEncoder().encode(s) {
UserDefaults.standard.set(data, forKey: key)
}
}
/// Whether the remote endpoint participates in resolution right now.
var remoteActiveInPolicy: Bool { provider.needsRemote && remoteEnabled }
/// Hosts to probe, in priority order (LAN first), de-duplicated and trimmed.
var candidates: [String] {
let d = directHost.trimmingCharacters(in: .whitespaces)
let r = remoteActiveInPolicy ? remoteHost.trimmingCharacters(in: .whitespaces) : ""
var seen = Set<String>(); var out: [String] = []
for h in [d, r] where !h.isEmpty && !seen.contains(h) { seen.insert(h); out.append(h) }
return out
}
var isConfigured: Bool { !candidates.isEmpty }
}