M4: NucleicRemote iPhone app — SwiftUI client over the shared protocol
A real iOS Xcode app (ios/NucleicRemote) linking the NucleicProtocol SwiftPM library as a local package. Builds for the iOS 27 simulator and launches to the pairing screen. - Transport: NWFrameChannel (NWConnection) + LANDiscovery (Bonjour _nucleic._tcp). - Engine: drives NucleicProtocol.SyncClient (Noise XXpsk0 pair / IK reconnect, hello/welcome, HostMsg→Event stream). - State: RemoteStore (ObservableObject) — the single on-device projection of host state; IdentityStore persists the device identity (Keychain) + pinned host. - UI (UX_IOS): attention-first SessionsView, SessionDetailView (transcript/diff + status-driven action area / composer), ApprovalCardView with Face ID gate on high-risk approvals + allow-always menu, PairingScannerView (AVFoundation QR), SettingsView, connection chip. Same status glyphs/semantics as the Mac. Add-iPhone QR display + server start live on the macOS side (follow-up); push / Live Activity are M5 (needs the relay). gitignore keeps this .xcodeproj despite the blanket *.xcodeproj rule. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsView: View {
|
||||
@EnvironmentObject var store: RemoteStore
|
||||
@State private var showScanner = false
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
Section("Connection") {
|
||||
LabeledContent("Status", value: store.connectivity.label)
|
||||
if !store.hostName.isEmpty {
|
||||
LabeledContent("Mac", value: store.hostName)
|
||||
}
|
||||
Button("Reconnect") { store.reconnect() }
|
||||
.disabled(!store.isPaired)
|
||||
}
|
||||
|
||||
Section("This device") {
|
||||
LabeledContent("Scope", value: store.grantedScope.rawValue.capitalized)
|
||||
LabeledContent("Key fingerprint", value: store.deviceFingerprint)
|
||||
.font(.footnote.monospaced())
|
||||
}
|
||||
|
||||
Section {
|
||||
Button {
|
||||
showScanner = true
|
||||
} label: {
|
||||
Label("Pair with a Mac", systemImage: "qrcode.viewfinder")
|
||||
}
|
||||
if store.isPaired {
|
||||
Button(role: .destructive) { store.unpair() } label: {
|
||||
Label("Unpair this device", systemImage: "minus.circle")
|
||||
}
|
||||
}
|
||||
} footer: {
|
||||
Text("Code and diffs are end-to-end encrypted. A relay (later) can't read them.")
|
||||
}
|
||||
}
|
||||
.navigationTitle("Settings")
|
||||
.sheet(isPresented: $showScanner) {
|
||||
PairingScannerView { payload in
|
||||
showScanner = false
|
||||
store.pair(with: payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Shown before pairing (UX_IOS §7): explain the flow and launch the scanner.
|
||||
struct PairingIntroView: View {
|
||||
@EnvironmentObject var store: RemoteStore
|
||||
@State private var showScanner = false
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 24) {
|
||||
Image(systemName: "qrcode.viewfinder").font(.system(size: 72)).foregroundStyle(.tint)
|
||||
Text("Pair with your Mac").font(.title2.weight(.semibold))
|
||||
Text("On your Mac, open Nucleic ▸ Settings ▸ Add iPhone to show a QR code, then scan it here.")
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundStyle(.secondary)
|
||||
.padding(.horizontal, 32)
|
||||
Button {
|
||||
showScanner = true
|
||||
} label: {
|
||||
Label("Scan QR code", systemImage: "camera").font(.headline).padding(.horizontal)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
.padding()
|
||||
.sheet(isPresented: $showScanner) {
|
||||
PairingScannerView { payload in
|
||||
showScanner = false
|
||||
store.pair(with: payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user