diff --git a/TFI/DesignSystem/MobileToolbarEnvironment.swift b/TFI/DesignSystem/MobileToolbarEnvironment.swift index ef16fc5b..b601e163 100644 --- a/TFI/DesignSystem/MobileToolbarEnvironment.swift +++ b/TFI/DesignSystem/MobileToolbarEnvironment.swift @@ -2,11 +2,12 @@ import SwiftUI struct MobileToolbarLayout: Equatable { var isTopBar = false + var isSidebar = false var verticalEdge: HorizontalEdge? var isVertical: Bool { verticalEdge != nil } var usesIconSwitcher: Bool { isTopBar || isVertical } - var showsWorkspaceTabs: Bool { isTopBar || isVertical } + var showsWorkspaceTabs: Bool { isTopBar || isSidebar || isVertical } } extension EnvironmentValues { @@ -19,11 +20,12 @@ private struct MobileToolbarEnvironment: ViewModifier { func body(content: Content) -> some View { if #available(iOS 27.1, *) { - content.modifier(MobileVerticalToolbarEnvironment(isTopBar: placement == .topBar)) + content.modifier( + MobileVerticalToolbarEnvironment(isTopBar: placement == .topBar, isSidebar: placement == .sidebar)) } else { content.environment( \.mobileToolbarLayout, - MobileToolbarLayout(isTopBar: placement == .topBar)) + MobileToolbarLayout(isTopBar: placement == .topBar, isSidebar: placement == .sidebar)) } } } @@ -31,12 +33,13 @@ private struct MobileToolbarEnvironment: ViewModifier { @available(iOS 27.1, *) private struct MobileVerticalToolbarEnvironment: ViewModifier { let isTopBar: Bool + let isSidebar: Bool @Environment(\.toolbarVerticalEdge) private var edge func body(content: Content) -> some View { content.environment( \.mobileToolbarLayout, - MobileToolbarLayout(isTopBar: isTopBar, verticalEdge: edge)) + MobileToolbarLayout(isTopBar: isTopBar, isSidebar: isSidebar, verticalEdge: edge)) } } diff --git a/TFI/Features/Workspaces/MobileWorkspaceView.swift b/TFI/Features/Workspaces/MobileWorkspaceView.swift index 276dcf35..9cfc3c17 100644 --- a/TFI/Features/Workspaces/MobileWorkspaceView.swift +++ b/TFI/Features/Workspaces/MobileWorkspaceView.swift @@ -145,7 +145,8 @@ struct MobileWorkspaceView: View { } .navigationTitle(workspace.descriptor.title) .navigationBarTitleDisplayMode(.inline) - .toolbar(toolbarLayout.showsWorkspaceTabs ? .visible : .hidden, for: .tabBar) + // Retained, inactive workspaces must not hide another destination's navigation when folding. + .toolbar(workspaceIsVisible && !toolbarLayout.showsWorkspaceTabs ? .hidden : .visible, for: .tabBar) .toolbar(removing: toolbarLayout.usesIconSwitcher ? .title : nil) .toolbar { if toolbarLayout.usesIconSwitcher { diff --git a/TFITests/MobileTerminalSearchTests.swift b/TFITests/MobileTerminalSearchTests.swift index 0dab35c8..fd7e7680 100644 --- a/TFITests/MobileTerminalSearchTests.swift +++ b/TFITests/MobileTerminalSearchTests.swift @@ -9,6 +9,40 @@ import UIKit @Suite(.serialized, .requiresKeyWindow) @MainActor struct MobileTerminalSearchIntegrationTests { + @Test func expandingVaultAfterWorkspaceSearchKeepsSidebarAvailable() async throws { + let fixture = try SearchFixture(kind: .ssh, fullRoot: true) + defer { fixture.close() } + let scene = try #require(fixture.scene) + fixture.resize(width: 430, height: 900, sizeClass: .compact) + try await fixture.settle() + let tabs = try #require(fixture.tabController) + let search = try #require(fixture.searchController) + search.isActive = true + try await fixture.settle() + scene.selectedDestination = .vault + try await fixture.settle() + + fixture.resize(width: 1024, height: 900, sizeClass: .regular) + if #available(iOS 27.0, *) { tabs.sidebar.preferredPlacement = .sidebar } + tabs.sidebar.isHidden = false + try await fixture.settle() + if #available(iOS 27.0, *) { #expect(tabs.sidebar.isAvailable) } + #expect(!search.isActive) + #expect(!tabs.sidebar.isHidden) + #expect(!tabs.isTabBarHidden) + #expect(scene.selectedDestination == .vault) + + // The retained workspace must also keep navigation available in sidebar mode. + scene.selectedDestination = .workspaces + try await fixture.settle() + #expect(!tabs.sidebar.isHidden) + #expect(!tabs.isTabBarHidden) + scene.selectedDestination = .vault + try await fixture.settle() + #expect(!tabs.sidebar.isHidden) + #expect(!tabs.isTabBarHidden) + } + @Test(arguments: [ConnectionHistoryEntry.Kind.ssh, .sftp]) func leavingWorkspaceSearchRestoresVaultNavigationAcrossResizing(kind: ConnectionHistoryEntry.Kind) async throws { let fixture = try SearchFixture(kind: kind, fullRoot: true) @@ -278,8 +312,9 @@ private final class SearchFixture { func settle() async throws { try await Task.sleep(for: .milliseconds(700)) } - func resize(width: CGFloat, height: CGFloat) { + func resize(width: CGFloat, height: CGFloat, sizeClass: UIUserInterfaceSizeClass? = nil) { let frame = CGRect(x: 0, y: 0, width: width, height: height) + if let sizeClass { host.traitOverrides.horizontalSizeClass = sizeClass } window.frame = frame host.view.frame = frame host.view.setNeedsLayout()