ios: some iPad fixes
This commit is contained in:
@@ -6,6 +6,7 @@ public struct SplitView: View {
|
|||||||
@Environment(\.scenePhase) private var scenePhase
|
@Environment(\.scenePhase) private var scenePhase
|
||||||
@State private var shouldRefreshOnForeground = false
|
@State private var shouldRefreshOnForeground = false
|
||||||
@State private var composerFocusRequest = 0
|
@State private var composerFocusRequest = 0
|
||||||
|
@State private var columnVisibility: NavigationSplitViewVisibility = .automatic
|
||||||
|
|
||||||
private var keyboardActions: SybilKeyboardActions? {
|
private var keyboardActions: SybilKeyboardActions? {
|
||||||
guard !viewModel.isCheckingSession, viewModel.isAuthenticated else {
|
guard !viewModel.isCheckingSession, viewModel.isAuthenticated else {
|
||||||
@@ -50,10 +51,16 @@ public struct SplitView: View {
|
|||||||
} else if horizontalSizeClass == .compact {
|
} else if horizontalSizeClass == .compact {
|
||||||
SybilPhoneShellView(viewModel: viewModel)
|
SybilPhoneShellView(viewModel: viewModel)
|
||||||
} else {
|
} else {
|
||||||
NavigationSplitView {
|
GeometryReader { proxy in
|
||||||
|
NavigationSplitView(columnVisibility: $columnVisibility) {
|
||||||
SybilSidebarView(viewModel: viewModel)
|
SybilSidebarView(viewModel: viewModel)
|
||||||
} detail: {
|
} detail: {
|
||||||
SybilWorkspaceView(viewModel: viewModel, composerFocusRequest: composerFocusRequest) {
|
SybilWorkspaceView(
|
||||||
|
viewModel: viewModel,
|
||||||
|
composerFocusRequest: composerFocusRequest,
|
||||||
|
navigationLeadingControl: splitNavigationLeadingControl(for: proxy.size),
|
||||||
|
onShowSidebar: showSidebar
|
||||||
|
) {
|
||||||
viewModel.startNewChat()
|
viewModel.startNewChat()
|
||||||
composerFocusRequest += 1
|
composerFocusRequest += 1
|
||||||
}
|
}
|
||||||
@@ -62,6 +69,7 @@ public struct SplitView: View {
|
|||||||
.tint(SybilTheme.primary)
|
.tint(SybilTheme.primary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.font(.sybil(.body))
|
.font(.sybil(.body))
|
||||||
.preferredColorScheme(.dark)
|
.preferredColorScheme(.dark)
|
||||||
.focusedSceneValue(\.sybilKeyboardActions, keyboardActions)
|
.focusedSceneValue(\.sybilKeyboardActions, keyboardActions)
|
||||||
@@ -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 {
|
public struct SybilCommands: Commands {
|
||||||
|
|||||||
@@ -264,8 +264,7 @@ private struct SybilPhoneDestinationView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
SybilWorkspaceView(
|
SybilWorkspaceView(
|
||||||
viewModel: viewModel,
|
viewModel: viewModel,
|
||||||
composerFocusRequest: composerFocusRequest,
|
composerFocusRequest: composerFocusRequest
|
||||||
usesCustomChatNavigation: route.isChatTranscript
|
|
||||||
) {
|
) {
|
||||||
viewModel.startNewChat()
|
viewModel.startNewChat()
|
||||||
composerFocusRequest += 1
|
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 isLoading: Bool
|
||||||
var isRunning: Bool
|
var isRunning: Bool
|
||||||
var isStartingChat: Bool = false
|
var isStartingChat: Bool = false
|
||||||
|
var topContentInset: CGFloat = 0
|
||||||
var onStartChat: (() -> Void)? = nil
|
var onStartChat: (() -> Void)? = nil
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -98,7 +99,8 @@ struct SybilSearchResultsView: View {
|
|||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.padding(.horizontal, 14)
|
.padding(.horizontal, 14)
|
||||||
.padding(.vertical, 20)
|
.padding(.top, 20 + topContentInset)
|
||||||
|
.padding(.bottom, 20)
|
||||||
}
|
}
|
||||||
.scrollDismissesKeyboard(.interactively)
|
.scrollDismissesKeyboard(.interactively)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|||||||
@@ -5,10 +5,18 @@ import SwiftUI
|
|||||||
import UniformTypeIdentifiers
|
import UniformTypeIdentifiers
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
enum SybilWorkspaceNavigationLeadingControl {
|
||||||
|
case back
|
||||||
|
case hidden
|
||||||
|
case showSidebar
|
||||||
|
}
|
||||||
|
|
||||||
struct SybilWorkspaceView: View {
|
struct SybilWorkspaceView: View {
|
||||||
@Bindable var viewModel: SybilViewModel
|
@Bindable var viewModel: SybilViewModel
|
||||||
var composerFocusRequest: Int = 0
|
var composerFocusRequest: Int = 0
|
||||||
var usesCustomChatNavigation: Bool = false
|
var usesCustomWorkspaceNavigation: Bool = true
|
||||||
|
var navigationLeadingControl: SybilWorkspaceNavigationLeadingControl = .back
|
||||||
|
var onShowSidebar: (() -> Void)? = nil
|
||||||
var onRequestNewChat: (() -> Void)? = nil
|
var onRequestNewChat: (() -> Void)? = nil
|
||||||
@FocusState private var composerFocused: Bool
|
@FocusState private var composerFocused: Bool
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
@@ -26,7 +34,7 @@ struct SybilWorkspaceView: View {
|
|||||||
@State private var newChatSwipeDidTriggerHaptic = false
|
@State private var newChatSwipeDidTriggerHaptic = false
|
||||||
@State private var newChatSwipeFeedbackGenerator: UIImpactFeedbackGenerator?
|
@State private var newChatSwipeFeedbackGenerator: UIImpactFeedbackGenerator?
|
||||||
|
|
||||||
private let customChatNavigationContentInset: CGFloat = 96
|
private let customWorkspaceNavigationContentInset: CGFloat = 96
|
||||||
|
|
||||||
private var isSettingsSelected: Bool {
|
private var isSettingsSelected: Bool {
|
||||||
if case .settings = viewModel.selectedItem {
|
if case .settings = viewModel.selectedItem {
|
||||||
@@ -39,8 +47,8 @@ struct SybilWorkspaceView: View {
|
|||||||
viewModel.errorMessage != nil
|
viewModel.errorMessage != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
private var showsCustomChatNavigation: Bool {
|
private var showsCustomWorkspaceNavigation: Bool {
|
||||||
usesCustomChatNavigation && !isSettingsSelected && !viewModel.isSearchMode
|
usesCustomWorkspaceNavigation && !isSettingsSelected
|
||||||
}
|
}
|
||||||
|
|
||||||
private var transcriptScrollContextID: String {
|
private var transcriptScrollContextID: String {
|
||||||
@@ -93,12 +101,12 @@ struct SybilWorkspaceView: View {
|
|||||||
}
|
}
|
||||||
.offset(x: newChatSwipeCompletionOffset)
|
.offset(x: newChatSwipeCompletionOffset)
|
||||||
.background(SybilTheme.background)
|
.background(SybilTheme.background)
|
||||||
.navigationTitle(showsCustomChatNavigation ? "" : viewModel.selectedTitle)
|
.navigationTitle(showsCustomWorkspaceNavigation ? "" : viewModel.selectedTitle)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbarRole(.editor)
|
.toolbarRole(.editor)
|
||||||
.toolbar(showsCustomChatNavigation ? .hidden : .visible, for: .navigationBar)
|
.toolbar(showsCustomWorkspaceNavigation ? .hidden : .visible, for: .navigationBar)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
if !isSettingsSelected && !showsCustomChatNavigation {
|
if !isSettingsSelected && !showsCustomWorkspaceNavigation {
|
||||||
ToolbarItem(placement: .topBarTrailing) {
|
ToolbarItem(placement: .topBarTrailing) {
|
||||||
if viewModel.isSearchMode {
|
if viewModel.isSearchMode {
|
||||||
searchModeChip
|
searchModeChip
|
||||||
@@ -124,10 +132,10 @@ struct SybilWorkspaceView: View {
|
|||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
workspaceContentStack
|
workspaceContentStack
|
||||||
|
|
||||||
if showsCustomChatNavigation {
|
if showsCustomWorkspaceNavigation {
|
||||||
SybilChatCharacterBackdrop(isBusy: viewModel.isSending)
|
SybilWorkspaceCharacterBackdrop(isBusy: viewModel.isSending)
|
||||||
.allowsHitTesting(false)
|
.allowsHitTesting(false)
|
||||||
customChatNavigationBar
|
customWorkspaceNavigationBar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,7 +157,8 @@ struct SybilWorkspaceView: View {
|
|||||||
search: viewModel.selectedSearch,
|
search: viewModel.selectedSearch,
|
||||||
isLoading: viewModel.isLoadingSelection,
|
isLoading: viewModel.isLoadingSelection,
|
||||||
isRunning: viewModel.isSending,
|
isRunning: viewModel.isSending,
|
||||||
isStartingChat: viewModel.isCreatingSearchChat
|
isStartingChat: viewModel.isCreatingSearchChat,
|
||||||
|
topContentInset: showsCustomWorkspaceNavigation ? customWorkspaceNavigationContentInset : 0
|
||||||
) {
|
) {
|
||||||
Task {
|
Task {
|
||||||
await viewModel.startChatFromSelectedSearch()
|
await viewModel.startChatFromSelectedSearch()
|
||||||
@@ -160,7 +169,7 @@ struct SybilWorkspaceView: View {
|
|||||||
messages: viewModel.displayedMessages,
|
messages: viewModel.displayedMessages,
|
||||||
isLoading: viewModel.isLoadingSelection,
|
isLoading: viewModel.isLoadingSelection,
|
||||||
isSending: viewModel.isSending,
|
isSending: viewModel.isSending,
|
||||||
topContentInset: showsCustomChatNavigation ? customChatNavigationContentInset : 0
|
topContentInset: showsCustomWorkspaceNavigation ? customWorkspaceNavigationContentInset : 0
|
||||||
)
|
)
|
||||||
.id(transcriptScrollContextID)
|
.id(transcriptScrollContextID)
|
||||||
}
|
}
|
||||||
@@ -193,15 +202,9 @@ struct SybilWorkspaceView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var customChatNavigationBar: some View {
|
private var customWorkspaceNavigationBar: some View {
|
||||||
HStack(spacing: 14) {
|
HStack(spacing: 14) {
|
||||||
Button {
|
workspaceNavigationLeadingControl
|
||||||
dismiss()
|
|
||||||
} label: {
|
|
||||||
SybilNavigationIcon(systemImage: "chevron.left")
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
.accessibilityLabel("Back")
|
|
||||||
|
|
||||||
Text(viewModel.selectedTitle)
|
Text(viewModel.selectedTitle)
|
||||||
.font(.sybil(size: 16, weight: .semibold))
|
.font(.sybil(size: 16, weight: .semibold))
|
||||||
@@ -211,7 +214,7 @@ struct SybilWorkspaceView: View {
|
|||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.multilineTextAlignment(.leading)
|
.multilineTextAlignment(.leading)
|
||||||
|
|
||||||
providerModelNavigationMenu
|
workspaceNavigationTrailingControl
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 16)
|
.padding(.horizontal, 16)
|
||||||
.padding(.top, 10)
|
.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) {
|
private func beginNewChatSwipe(containerWidth: CGFloat) {
|
||||||
let update = {
|
let update = {
|
||||||
newChatSwipeContainerWidth = max(containerWidth, 1)
|
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 {
|
private func providerModelMenu<Label: View>(@ViewBuilder label: @escaping () -> Label) -> some View {
|
||||||
Menu {
|
Menu {
|
||||||
providerModelMenuItems
|
providerModelMenuItems
|
||||||
@@ -983,7 +1028,7 @@ private struct SybilNavigationFadeBackground: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct SybilChatCharacterBackdrop: View {
|
private struct SybilWorkspaceCharacterBackdrop: View {
|
||||||
var isBusy: Bool
|
var isBusy: Bool
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|||||||
Reference in New Issue
Block a user