fix(apple/tvOS): pushed routes instead of modal covers — the Settings-app navigation feel
ci / rust (push) Has been cancelled
ci / rust (push) Has been cancelled
Add Host, Settings and PIN pairing were fullScreenCover overlays, which is why navigating felt unlike the system Settings app (no push animation, no Menu-pops-a-level semantics). They are now navigationDestination ROUTES pushed inside the home NavigationStack: - the system push/pop animation and Menu-button back navigation come for free; - the Settings pickers' navigationLink pushes reuse the same stack (its inner NavigationStack wrapper is gone, as is the tvOS Done row — Menu pops, like Settings); - Add Host is a real full-screen page (system navigation title, Settings-style rows on the standard backdrop) instead of a floating dialog, same for the pairing page; - the thickMaterial cover backdrops became unnecessary and are gone. The system keyboard entries stay as covers — that presentation is system-owned either way. iOS/macOS keep their sheets. Verified by screenshot: Add Host renders as a pushed full-screen route with the title top-center. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,8 +23,6 @@ struct AddHostSheet: View {
|
||||
// No inline text editing on tvOS — Settings-style value rows; pressing one
|
||||
// raises the SYSTEM fullscreen keyboard (TVTextEntry).
|
||||
VStack(spacing: 24) {
|
||||
Text("Add Host")
|
||||
.font(.title3.weight(.semibold))
|
||||
TVFieldRow(
|
||||
label: "Name", value: name, placeholder: "Optional"
|
||||
) { editing = .name }
|
||||
@@ -43,6 +41,7 @@ struct AddHostSheet: View {
|
||||
}
|
||||
.frame(maxWidth: 1000)
|
||||
.padding(60)
|
||||
.navigationTitle("Add Host")
|
||||
.fullScreenCover(item: $editing) { field in
|
||||
switch field {
|
||||
case .name:
|
||||
|
||||
@@ -55,18 +55,7 @@ struct ContentView: View {
|
||||
// On the outer Group so the sheet survives the trust-prompt → home transition
|
||||
// (the "Pair with PIN instead" path disconnects first — the host's accept loop
|
||||
// is sequential, a pairing connection would queue behind the live session).
|
||||
#if os(tvOS)
|
||||
.fullScreenCover(item: $pairingTarget) { host in
|
||||
PairSheet(host: host) { fingerprint in
|
||||
guard pairingTarget?.id == host.id else { return }
|
||||
store.pin(host.id, fingerprint: fingerprint)
|
||||
var pinned = host
|
||||
pinned.pinnedSHA256 = fingerprint
|
||||
connect(pinned)
|
||||
}
|
||||
.background(.thickMaterial, ignoresSafeAreaEdges: .all)
|
||||
}
|
||||
#else
|
||||
#if !os(tvOS)
|
||||
.sheet(item: $pairingTarget) { host in
|
||||
PairSheet(host: host) { fingerprint in
|
||||
// Backstop against a stale ceremony surfacing after dismissal (PairSheet
|
||||
@@ -149,6 +138,25 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
.navigationTitle("Punktfunkempfänger")
|
||||
#if os(tvOS)
|
||||
// Pushed routes — the Settings-app navigation feel (push animation, Menu
|
||||
// pops) instead of modal overlays.
|
||||
.navigationDestination(isPresented: $showAddHost) {
|
||||
AddHostSheet { store.add($0) }
|
||||
}
|
||||
.navigationDestination(isPresented: $showSettings) {
|
||||
SettingsView()
|
||||
}
|
||||
.navigationDestination(item: $pairingTarget) { host in
|
||||
PairSheet(host: host) { fingerprint in
|
||||
guard pairingTarget?.id == host.id else { return }
|
||||
store.pin(host.id, fingerprint: fingerprint)
|
||||
var pinned = host
|
||||
pinned.pinnedSHA256 = fingerprint
|
||||
connect(pinned)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if !os(tvOS)
|
||||
.toolbar {
|
||||
#if os(iOS)
|
||||
@@ -173,20 +181,7 @@ struct ContentView: View {
|
||||
#if os(macOS)
|
||||
.frame(minWidth: 480, minHeight: 360)
|
||||
#endif
|
||||
#if os(tvOS)
|
||||
// tvOS forms/lists have CLEAR backgrounds and a cover only shows what the
|
||||
// presented view paints — back them with the standard tv blur-over-content.
|
||||
.fullScreenCover(isPresented: $showAddHost) {
|
||||
AddHostSheet { store.add($0) }
|
||||
.background(.thickMaterial, ignoresSafeAreaEdges: .all)
|
||||
}
|
||||
.fullScreenCover(isPresented: $showSettings) {
|
||||
NavigationStack {
|
||||
SettingsView()
|
||||
}
|
||||
.background(.thickMaterial, ignoresSafeAreaEdges: .all)
|
||||
}
|
||||
#else
|
||||
#if !os(tvOS)
|
||||
.sheet(isPresented: $showAddHost) {
|
||||
AddHostSheet { store.add($0) }
|
||||
}
|
||||
|
||||
@@ -44,9 +44,6 @@ struct PairSheet: View {
|
||||
var body: some View {
|
||||
#if os(tvOS)
|
||||
VStack(spacing: 24) {
|
||||
Label("Pair with \(host.displayName)", systemImage: "lock.shield")
|
||||
.font(.title3.weight(.semibold))
|
||||
.foregroundStyle(.tint)
|
||||
Text("The host prints the PIN when pairing is armed "
|
||||
+ "(--allow-pairing, \u{201C}PAIRING ARMED\u{201D} in its log). "
|
||||
+ "Pairing verifies both sides at once — no fingerprint comparison "
|
||||
@@ -80,6 +77,7 @@ struct PairSheet: View {
|
||||
}
|
||||
.frame(maxWidth: 1000)
|
||||
.padding(60)
|
||||
.navigationTitle("Pair with \(host.displayName)")
|
||||
.onDisappear { token.cancelled = true }
|
||||
.fullScreenCover(item: $editing) { field in
|
||||
switch field {
|
||||
|
||||
@@ -87,9 +87,6 @@ struct SettingsView: View {
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
Section {
|
||||
Button("Done") { dismiss() }
|
||||
}
|
||||
}
|
||||
.navigationTitle("Settings")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user