ios: some iPad fixes

This commit is contained in:
2026-05-03 18:38:16 -07:00
parent ae783020ef
commit 53a3b722ec
4 changed files with 97 additions and 44 deletions

View File

@@ -6,6 +6,7 @@ public struct SplitView: View {
@Environment(\.scenePhase) private var scenePhase
@State private var shouldRefreshOnForeground = false
@State private var composerFocusRequest = 0
@State private var columnVisibility: NavigationSplitViewVisibility = .automatic
private var keyboardActions: SybilKeyboardActions? {
guard !viewModel.isCheckingSession, viewModel.isAuthenticated else {
@@ -50,16 +51,23 @@ public struct SplitView: View {
} else if horizontalSizeClass == .compact {
SybilPhoneShellView(viewModel: viewModel)
} else {
NavigationSplitView {
SybilSidebarView(viewModel: viewModel)
} detail: {
SybilWorkspaceView(viewModel: viewModel, composerFocusRequest: composerFocusRequest) {
viewModel.startNewChat()
composerFocusRequest += 1
GeometryReader { proxy in
NavigationSplitView(columnVisibility: $columnVisibility) {
SybilSidebarView(viewModel: viewModel)
} detail: {
SybilWorkspaceView(
viewModel: viewModel,
composerFocusRequest: composerFocusRequest,
navigationLeadingControl: splitNavigationLeadingControl(for: proxy.size),
onShowSidebar: showSidebar
) {
viewModel.startNewChat()
composerFocusRequest += 1
}
}
.navigationSplitViewStyle(.balanced)
.tint(SybilTheme.primary)
}
.navigationSplitViewStyle(.balanced)
.tint(SybilTheme.primary)
}
}
.font(.sybil(.body))
@@ -93,6 +101,16 @@ public struct SplitView: View {
}
}
}
private func splitNavigationLeadingControl(for size: CGSize) -> SybilWorkspaceNavigationLeadingControl {
return size.width < size.height ? .showSidebar : .hidden
}
private func showSidebar() {
withAnimation(.easeInOut(duration: 0.22)) {
columnVisibility = .all
}
}
}
public struct SybilCommands: Commands {

View File

@@ -264,8 +264,7 @@ private struct SybilPhoneDestinationView: View {
var body: some View {
SybilWorkspaceView(
viewModel: viewModel,
composerFocusRequest: composerFocusRequest,
usesCustomChatNavigation: route.isChatTranscript
composerFocusRequest: composerFocusRequest
) {
viewModel.startNewChat()
composerFocusRequest += 1
@@ -297,14 +296,3 @@ private struct SybilPhoneDestinationView: View {
}
}
}
private extension PhoneRoute {
var isChatTranscript: Bool {
switch self {
case .chat, .draftChat:
return true
case .search, .draftSearch, .settings:
return false
}
}
}

View File

@@ -6,6 +6,7 @@ struct SybilSearchResultsView: View {
var isLoading: Bool
var isRunning: Bool
var isStartingChat: Bool = false
var topContentInset: CGFloat = 0
var onStartChat: (() -> Void)? = nil
var body: some View {
@@ -98,7 +99,8 @@ struct SybilSearchResultsView: View {
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 14)
.padding(.vertical, 20)
.padding(.top, 20 + topContentInset)
.padding(.bottom, 20)
}
.scrollDismissesKeyboard(.interactively)
.frame(maxWidth: .infinity, alignment: .leading)

View File

@@ -5,10 +5,18 @@ import SwiftUI
import UniformTypeIdentifiers
import UIKit
enum SybilWorkspaceNavigationLeadingControl {
case back
case hidden
case showSidebar
}
struct SybilWorkspaceView: View {
@Bindable var viewModel: SybilViewModel
var composerFocusRequest: Int = 0
var usesCustomChatNavigation: Bool = false
var usesCustomWorkspaceNavigation: Bool = true
var navigationLeadingControl: SybilWorkspaceNavigationLeadingControl = .back
var onShowSidebar: (() -> Void)? = nil
var onRequestNewChat: (() -> Void)? = nil
@FocusState private var composerFocused: Bool
@Environment(\.dismiss) private var dismiss
@@ -26,7 +34,7 @@ struct SybilWorkspaceView: View {
@State private var newChatSwipeDidTriggerHaptic = false
@State private var newChatSwipeFeedbackGenerator: UIImpactFeedbackGenerator?
private let customChatNavigationContentInset: CGFloat = 96
private let customWorkspaceNavigationContentInset: CGFloat = 96
private var isSettingsSelected: Bool {
if case .settings = viewModel.selectedItem {
@@ -39,8 +47,8 @@ struct SybilWorkspaceView: View {
viewModel.errorMessage != nil
}
private var showsCustomChatNavigation: Bool {
usesCustomChatNavigation && !isSettingsSelected && !viewModel.isSearchMode
private var showsCustomWorkspaceNavigation: Bool {
usesCustomWorkspaceNavigation && !isSettingsSelected
}
private var transcriptScrollContextID: String {
@@ -93,12 +101,12 @@ struct SybilWorkspaceView: View {
}
.offset(x: newChatSwipeCompletionOffset)
.background(SybilTheme.background)
.navigationTitle(showsCustomChatNavigation ? "" : viewModel.selectedTitle)
.navigationTitle(showsCustomWorkspaceNavigation ? "" : viewModel.selectedTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbarRole(.editor)
.toolbar(showsCustomChatNavigation ? .hidden : .visible, for: .navigationBar)
.toolbar(showsCustomWorkspaceNavigation ? .hidden : .visible, for: .navigationBar)
.toolbar {
if !isSettingsSelected && !showsCustomChatNavigation {
if !isSettingsSelected && !showsCustomWorkspaceNavigation {
ToolbarItem(placement: .topBarTrailing) {
if viewModel.isSearchMode {
searchModeChip
@@ -124,10 +132,10 @@ struct SybilWorkspaceView: View {
ZStack(alignment: .top) {
workspaceContentStack
if showsCustomChatNavigation {
SybilChatCharacterBackdrop(isBusy: viewModel.isSending)
if showsCustomWorkspaceNavigation {
SybilWorkspaceCharacterBackdrop(isBusy: viewModel.isSending)
.allowsHitTesting(false)
customChatNavigationBar
customWorkspaceNavigationBar
}
}
}
@@ -149,7 +157,8 @@ struct SybilWorkspaceView: View {
search: viewModel.selectedSearch,
isLoading: viewModel.isLoadingSelection,
isRunning: viewModel.isSending,
isStartingChat: viewModel.isCreatingSearchChat
isStartingChat: viewModel.isCreatingSearchChat,
topContentInset: showsCustomWorkspaceNavigation ? customWorkspaceNavigationContentInset : 0
) {
Task {
await viewModel.startChatFromSelectedSearch()
@@ -160,7 +169,7 @@ struct SybilWorkspaceView: View {
messages: viewModel.displayedMessages,
isLoading: viewModel.isLoadingSelection,
isSending: viewModel.isSending,
topContentInset: showsCustomChatNavigation ? customChatNavigationContentInset : 0
topContentInset: showsCustomWorkspaceNavigation ? customWorkspaceNavigationContentInset : 0
)
.id(transcriptScrollContextID)
}
@@ -193,15 +202,9 @@ struct SybilWorkspaceView: View {
}
}
private var customChatNavigationBar: some View {
private var customWorkspaceNavigationBar: some View {
HStack(spacing: 14) {
Button {
dismiss()
} label: {
SybilNavigationIcon(systemImage: "chevron.left")
}
.buttonStyle(.plain)
.accessibilityLabel("Back")
workspaceNavigationLeadingControl
Text(viewModel.selectedTitle)
.font(.sybil(size: 16, weight: .semibold))
@@ -211,7 +214,7 @@ struct SybilWorkspaceView: View {
.frame(maxWidth: .infinity, alignment: .leading)
.multilineTextAlignment(.leading)
providerModelNavigationMenu
workspaceNavigationTrailingControl
}
.padding(.horizontal, 16)
.padding(.top, 10)
@@ -222,6 +225,32 @@ struct SybilWorkspaceView: View {
}
}
@ViewBuilder
private var workspaceNavigationLeadingControl: some View {
switch navigationLeadingControl {
case .back:
Button {
dismiss()
} label: {
SybilNavigationIcon(systemImage: "chevron.left")
}
.buttonStyle(.plain)
.accessibilityLabel("Back")
case .showSidebar:
Button {
onShowSidebar?()
} label: {
SybilNavigationIcon(systemImage: "sidebar.left")
}
.buttonStyle(.plain)
.accessibilityLabel("Show sidebar")
case .hidden:
EmptyView()
}
}
private func beginNewChatSwipe(containerWidth: CGFloat) {
let update = {
newChatSwipeContainerWidth = max(containerWidth, 1)
@@ -367,6 +396,22 @@ struct SybilWorkspaceView: View {
}
}
@ViewBuilder
private var workspaceNavigationTrailingControl: some View {
if viewModel.isSearchMode {
searchModeNavigationLabel
} else {
providerModelNavigationMenu
}
}
private var searchModeNavigationLabel: some View {
Label("Search", systemImage: "globe")
.font(.sybil(.caption, weight: .medium))
.foregroundStyle(SybilTheme.accent)
.lineLimit(1)
}
private func providerModelMenu<Label: View>(@ViewBuilder label: @escaping () -> Label) -> some View {
Menu {
providerModelMenuItems
@@ -983,7 +1028,7 @@ private struct SybilNavigationFadeBackground: View {
}
}
private struct SybilChatCharacterBackdrop: View {
private struct SybilWorkspaceCharacterBackdrop: View {
var isBusy: Bool
var body: some View {