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>
This commit is contained in:
73
Jarvis/Connectivity/ConnectivitySettings.swift
Normal file
73
Jarvis/Connectivity/ConnectivitySettings.swift
Normal file
@@ -0,0 +1,73 @@
|
||||
// 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 }
|
||||
}
|
||||
Reference in New Issue
Block a user