Files
punktfunk/clients/apple/Sources/PunktfunkClient/HostStore.swift
T
enricobuehler ee12e535ee
ci / rust (push) Has been cancelled
feat(apple): styling pass — dark-mode accent, recent-host state, glass HUD, security-sheet polish
Working through the brand-color follow-ups:

- AccentColor gains a dark-appearance variant (#8678F5 — the brand violet lifted one
  step toward the icon's light periwinkle) so tinted controls keep contrast on dark.
- Host cards remember sessions: StoredHost.lastConnected (set when a session reaches
  streaming) renders as a "Connected … ago" relative-time line, and the most recent
  host's card carries a subtle accent ring — the grid finally has hierarchy.
- The HUD swaps the pre-glass black-50% rectangle for .regularMaterial with an accent
  live-dot; hint lines use semantic .secondary instead of opacity.
- Security moments: the trust card's lock.shield and the pairing sheet's header take
  the brand tint; the PIN field is larger monospaced and uses the number pad on iOS.

Icon ↔ accent decision: the accent stays the exact brand #6656F2; the Icon Composer
layers keep their adjacent palette (#6C5BF3 family) — close enough to read as one
brand, and the icon remains the design-tool source of truth.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 12:59:55 +02:00

75 lines
2.5 KiB
Swift

// Saved hosts + their pinned identities, persisted as JSON in UserDefaults.
//
// Trust model (client side of punktfunk/1): the host serves a persistent certificate and
// logs its SHA-256 fingerprint at startup. The pin lands here one of two ways the
// trust-on-first-use prompt (user compares the observed fingerprint against the host's
// log) or the SPAKE2 PIN pairing ceremony (PairSheet; mutually verified, and the host
// stores our identity from ClientIdentityStore in return). Every later connect passes
// the pin into punktfunk-core, which refuses a host whose identity changed. Hosts running
// --require-pairing only admit paired clients, so for them pairing is the only way in.
import Foundation
import SwiftUI
struct StoredHost: Identifiable, Codable, Hashable {
var id = UUID()
var name: String
var address: String
var port: UInt16 = 9777
/// SHA-256 of the host's certificate, set after the user explicitly trusted it.
var pinnedSHA256: Data?
/// Last time a streaming session actually started (nil until the first one).
var lastConnected: Date?
var displayName: String { name.isEmpty ? address : name }
}
@MainActor
final class HostStore: ObservableObject {
private static let key = "punktfunk.hosts"
@Published var hosts: [StoredHost] {
didSet { persist() }
}
init() {
if let data = UserDefaults.standard.data(forKey: Self.key),
let decoded = try? JSONDecoder().decode([StoredHost].self, from: data) {
hosts = decoded
} else {
hosts = []
}
}
func add(_ host: StoredHost) {
hosts.append(host)
}
func remove(_ host: StoredHost) {
hosts.removeAll { $0.id == host.id }
}
func markConnected(_ hostID: UUID) {
guard let i = hosts.firstIndex(where: { $0.id == hostID }) else { return }
hosts[i].lastConnected = Date()
}
func pin(_ hostID: UUID, fingerprint: Data) {
guard let i = hosts.firstIndex(where: { $0.id == hostID }) else { return }
hosts[i].pinnedSHA256 = fingerprint
}
/// Drop the pinned identity (e.g. after a legitimate host reinstall) the next
/// connect goes through the trust prompt again.
func forgetIdentity(_ host: StoredHost) {
guard let i = hosts.firstIndex(where: { $0.id == host.id }) else { return }
hosts[i].pinnedSHA256 = nil
}
private func persist() {
if let data = try? JSONEncoder().encode(hosts) {
UserDefaults.standard.set(data, forKey: Self.key)
}
}
}