ios: some iPad fixes
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user