From 6b50184bcc789d6b78d857fd3c391a7950fb26e1 Mon Sep 17 00:00:00 2001 From: Trey T Date: Thu, 16 Apr 2026 21:33:50 -0500 Subject: [PATCH] =?UTF-8?q?Kill=20the=20NavigationStack=20on=20the=20list?= =?UTF-8?q?=20=E2=80=94=20that=20was=20the=20floating-chrome=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iOS 26's NavigationStack reserves space for its floating Liquid Glass nav bar even with `.toolbar(.hidden, for: .navigationBar)` — that's why "Screens" landed ~180pt down from the dynamic island with a wedge of black above it. Removed the NavigationStack from the list entirely. Layout is now a plain top-anchored VStack: top chrome (gear + Screens + plus + search) flush with the safe-area top, then a ScrollView with LazyVStack of cards filling the rest of the screen. Sessions present via fullScreenCover instead of NavigationLink, so we don't need NavigationStack here at all. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../VNCUI/List/ConnectionListView.swift | 128 +++++++++--------- 1 file changed, 62 insertions(+), 66 deletions(-) diff --git a/Packages/VNCUI/Sources/VNCUI/List/ConnectionListView.swift b/Packages/VNCUI/Sources/VNCUI/List/ConnectionListView.swift index 189fb1b..1bf505f 100644 --- a/Packages/VNCUI/Sources/VNCUI/List/ConnectionListView.swift +++ b/Packages/VNCUI/Sources/VNCUI/List/ConnectionListView.swift @@ -6,67 +6,45 @@ public struct ConnectionListView: View { @Environment(\.modelContext) private var modelContext @Environment(\.openWindow) private var openWindow @Query(sort: \SavedConnection.displayName) private var connections: [SavedConnection] + @State private var discovery = DiscoveryService() @State private var showingAdd = false @State private var showingSettings = false @State private var addPrefill: AddConnectionPrefill? @State private var editingConnection: SavedConnection? - @State private var path: [SessionRoute] = [] + @State private var sessionConnection: SavedConnection? @State private var resolvingHostID: String? @State private var search = "" public init() {} public var body: some View { - NavigationStack(path: $path) { - ZStack(alignment: .top) { - backgroundGradient.ignoresSafeArea() - - ScrollView { - VStack(spacing: 22) { - topChrome - - if !discovery.hosts.isEmpty || discovery.isBrowsing { - discoveredSection - } - - savedSection - } - .padding(.horizontal, 16) - .padding(.top, 8) - .padding(.bottom, 32) - } - #if os(iOS) - .scrollIndicators(.hidden) - #endif - } - #if os(iOS) - .toolbar(.hidden, for: .navigationBar) - #endif - .sheet(isPresented: $showingAdd) { - AddConnectionView(prefill: addPrefill) - } - .sheet(item: $editingConnection) { connection in - AddConnectionView(editing: connection) - } - .sheet(isPresented: $showingSettings) { - SettingsView() - } - .navigationDestination(for: SessionRoute.self) { route in - if let connection = connection(with: route.connectionID) { - SessionView(connection: connection) - } else { - ContentUnavailableView("Connection unavailable", - systemImage: "exclamationmark.triangle") - } - } - .task { - discovery.start() - } - .onDisappear { - discovery.stop() - } + VStack(spacing: 0) { + topChrome + contentList } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) + .background(backgroundGradient.ignoresSafeArea()) + .sheet(isPresented: $showingAdd) { + AddConnectionView(prefill: addPrefill) + } + .sheet(item: $editingConnection) { connection in + AddConnectionView(editing: connection) + } + .sheet(isPresented: $showingSettings) { + SettingsView() + } + #if os(iOS) + .fullScreenCover(item: $sessionConnection) { connection in + SessionView(connection: connection) + } + #else + .sheet(item: $sessionConnection) { connection in + SessionView(connection: connection) + } + #endif + .task { discovery.start() } + .onDisappear { discovery.stop() } } // MARK: Background @@ -81,29 +59,28 @@ public struct ConnectionListView: View { startPoint: .top, endPoint: .bottom ) - // soft accent bloom up top so the floating buttons feel anchored RadialGradient( - colors: [Color.blue.opacity(0.22), .clear], - center: .init(x: 0.5, y: 0.0), - startRadius: 20, - endRadius: 320 + colors: [Color.blue.opacity(0.20), .clear], + center: .init(x: 0.5, y: 0.05), + startRadius: 10, + endRadius: 360 ) .blendMode(.screen) .allowsHitTesting(false) } } - // MARK: Top chrome (custom — replaces nav bar) + // MARK: Top chrome — flush with safe area top private var topChrome: some View { - VStack(spacing: 14) { + VStack(spacing: 12) { HStack(alignment: .center) { circularGlassButton(systemName: "gearshape", label: "Settings") { showingSettings = true } Spacer() Text("Screens") - .font(.system(size: 28, weight: .bold, design: .rounded)) + .font(.system(size: 26, weight: .bold, design: .rounded)) .foregroundStyle(.white) Spacer() circularGlassButton(systemName: "plus", label: "Add connection") { @@ -111,10 +88,11 @@ public struct ConnectionListView: View { showingAdd = true } } - searchField } - .padding(.top, 4) + .padding(.horizontal, 16) + .padding(.top, 8) + .padding(.bottom, 12) } private func circularGlassButton(systemName: String, @@ -124,7 +102,7 @@ public struct ConnectionListView: View { Image(systemName: systemName) .font(.callout.weight(.semibold)) .foregroundStyle(.white) - .frame(width: 40, height: 40) + .frame(width: 38, height: 38) } .buttonStyle(.plain) .glassSurface(in: Circle()) @@ -160,6 +138,26 @@ public struct ConnectionListView: View { .glassSurface(in: Capsule()) } + // MARK: Content + + private var contentList: some View { + ScrollView { + LazyVStack(spacing: 22) { + if !discovery.hosts.isEmpty || discovery.isBrowsing { + discoveredSection + } + savedSection + Color.clear.frame(height: 1) // bottom anchor + } + .padding(.horizontal, 16) + .padding(.top, 4) + .padding(.bottom, 24) + } + #if os(iOS) + .scrollIndicators(.hidden) + #endif + } + // MARK: Discovered private var discoveredSection: some View { @@ -257,7 +255,7 @@ public struct ConnectionListView: View { Text("No saved connections") .font(.headline) .foregroundStyle(.white) - Text("Tap the + button up top to add a Mac, Linux box, or Raspberry Pi.") + Text("Tap + at the top to add a Mac, Linux box, or Raspberry Pi.") .font(.callout) .foregroundStyle(.white.opacity(0.65)) .multilineTextAlignment(.center) @@ -299,7 +297,9 @@ public struct ConnectionListView: View { } private func connectionRow(_ connection: SavedConnection) -> some View { - NavigationLink(value: SessionRoute(connectionID: connection.id)) { + Button { + sessionConnection = connection + } label: { ConnectionCard(connection: connection) .padding(14) } @@ -379,10 +379,6 @@ public struct ConnectionListView: View { showingAdd = true } - private func connection(with id: UUID) -> SavedConnection? { - connections.first(where: { $0.id == id }) - } - private func delete(_ connection: SavedConnection) { try? KeychainService().deletePassword(account: connection.keychainTag) modelContext.delete(connection)