ios: mac catalyst kb shortcuts

This commit is contained in:
2026-05-02 23:50:51 -07:00
parent 29e340fd08
commit 2f265fd847
3 changed files with 109 additions and 0 deletions

View File

@@ -9,5 +9,8 @@ struct SybilApp: App
WindowGroup { WindowGroup {
SplitView() SplitView()
} }
.commands {
SybilCommands()
}
} }
} }

View File

@@ -7,6 +7,29 @@ public struct SplitView: View {
@State private var shouldRefreshOnForeground = false @State private var shouldRefreshOnForeground = false
@State private var composerFocusRequest = 0 @State private var composerFocusRequest = 0
private var keyboardActions: SybilKeyboardActions? {
guard !viewModel.isCheckingSession, viewModel.isAuthenticated else {
return nil
}
return SybilKeyboardActions(
newChat: {
viewModel.startNewChat()
composerFocusRequest += 1
},
newSearch: {
viewModel.startNewSearch()
composerFocusRequest += 1
},
previousConversation: {
viewModel.selectPreviousSidebarItem()
},
nextConversation: {
viewModel.selectNextSidebarItem()
}
)
}
@MainActor public init() { @MainActor public init() {
SybilFontRegistry.registerIfNeeded() SybilFontRegistry.registerIfNeeded()
SybilTheme.applySystemAppearance() SybilTheme.applySystemAppearance()
@@ -41,6 +64,7 @@ public struct SplitView: View {
} }
.font(.sybil(.body)) .font(.sybil(.body))
.preferredColorScheme(.dark) .preferredColorScheme(.dark)
.focusedSceneValue(\.sybilKeyboardActions, keyboardActions)
.task { .task {
await viewModel.bootstrap() await viewModel.bootstrap()
} }
@@ -67,3 +91,57 @@ public struct SplitView: View {
} }
} }
} }
public struct SybilCommands: Commands {
@FocusedValue(\.sybilKeyboardActions) private var keyboardActions
public init() {}
public var body: some Commands {
CommandGroup(replacing: .newItem) {
Button("New Chat") {
keyboardActions?.newChat()
}
.keyboardShortcut("n", modifiers: .command)
.disabled(keyboardActions == nil)
Button("New Search") {
keyboardActions?.newSearch()
}
.keyboardShortcut("n", modifiers: [.command, .shift])
.disabled(keyboardActions == nil)
}
CommandMenu("Conversation") {
Button("Previous Conversation") {
keyboardActions?.previousConversation()
}
.keyboardShortcut("[", modifiers: .command)
.disabled(keyboardActions == nil)
Button("Next Conversation") {
keyboardActions?.nextConversation()
}
.keyboardShortcut("]", modifiers: .command)
.disabled(keyboardActions == nil)
}
}
}
private struct SybilKeyboardActions {
var newChat: () -> Void
var newSearch: () -> Void
var previousConversation: () -> Void
var nextConversation: () -> Void
}
private struct SybilKeyboardActionsKey: FocusedValueKey {
typealias Value = SybilKeyboardActions
}
private extension FocusedValues {
var sybilKeyboardActions: SybilKeyboardActions? {
get { self[SybilKeyboardActionsKey.self] }
set { self[SybilKeyboardActionsKey.self] = newValue }
}
}

View File

@@ -443,6 +443,34 @@ final class SybilViewModel {
} }
} }
func selectPreviousSidebarItem() {
selectAdjacentSidebarItem(offset: -1)
}
func selectNextSidebarItem() {
selectAdjacentSidebarItem(offset: 1)
}
private func selectAdjacentSidebarItem(offset: Int) {
let items = sidebarItems
guard !items.isEmpty else {
return
}
let currentIndex = selectedItem.flatMap { selection in
items.firstIndex { $0.selection == selection }
}
let startingIndex = currentIndex ?? (offset < 0 ? items.count : -1)
let nextIndex = (startingIndex + offset + items.count) % items.count
let nextSelection = items[nextIndex].selection
guard draftKind != nil || selectedItem != nextSelection else {
return
}
select(nextSelection)
}
func deleteItem(_ selection: SidebarSelection) async { func deleteItem(_ selection: SidebarSelection) async {
guard isAuthenticated else { guard isAuthenticated else {
return return