From b9c5c3446aaa32b8c5da7ffc65fd63dfb8102f74 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Mon, 30 Mar 2026 13:01:21 +0300 Subject: [PATCH] Move the global search shortcut to application commands so it's more reliable with multiple windows --- ElementX/Sources/Application/Application.swift | 7 +++++++ ElementX/Sources/Application/Navigation/AppRoutes.swift | 2 ++ .../Sources/FlowCoordinators/ChatsTabFlowCoordinator.swift | 4 ++-- .../EncryptionSettingsFlowCoordinator.swift | 3 ++- .../Sources/FlowCoordinators/RoomFlowCoordinator.swift | 2 +- .../FlowCoordinators/RoomMembersFlowCoordinator.swift | 2 +- .../FlowCoordinators/UserSessionFlowCoordinator.swift | 2 +- .../Sources/Screens/HomeScreen/HomeScreenCoordinator.swift | 3 --- ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift | 2 -- .../Sources/Screens/HomeScreen/HomeScreenViewModel.swift | 2 -- .../Screens/HomeScreen/View/HomeScreenContent.swift | 6 ------ UnitTests/Sources/HomeScreenViewModelTests.swift | 2 -- 12 files changed, 16 insertions(+), 21 deletions(-) diff --git a/ElementX/Sources/Application/Application.swift b/ElementX/Sources/Application/Application.swift index 12f063343..9c1b99263 100644 --- a/ElementX/Sources/Application/Application.swift +++ b/ElementX/Sources/Application/Application.swift @@ -64,6 +64,13 @@ struct Application: App { } .keyboardShortcut(",", modifiers: .command) } + + CommandGroup(after: .windowArrangement) { + Button("Global Search") { + appCoordinator.handleAppRoute(.globalSearch, windowType: nil) + } + .keyboardShortcut("k", modifiers: [.command]) + } } // This is invoked in response of the WindowManager receiving a register diff --git a/ElementX/Sources/Application/Navigation/AppRoutes.swift b/ElementX/Sources/Application/Navigation/AppRoutes.swift index c32caf347..32d8743cf 100644 --- a/ElementX/Sources/Application/Navigation/AppRoutes.swift +++ b/ElementX/Sources/Application/Navigation/AppRoutes.swift @@ -53,6 +53,8 @@ enum AppRoute: Hashable { case transferOwnership(roomID: String) /// A thread within a room, only to be used to handle tap on notification for threaded events. case thread(roomID: String, threadRootEventID: String, focusEventID: String?) + /// The global search screen + case globalSearch /// Whether or not the route should be handled by the authentication flow. var isAuthenticationRoute: Bool { diff --git a/ElementX/Sources/FlowCoordinators/ChatsTabFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/ChatsTabFlowCoordinator.swift index fa1214436..aeb8ba0cc 100644 --- a/ElementX/Sources/FlowCoordinators/ChatsTabFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/ChatsTabFlowCoordinator.swift @@ -170,6 +170,8 @@ class ChatsTabFlowCoordinator: FlowCoordinatorProtocol { } else { stateMachine.processEvent(.presentTransferOwnershipScreen(roomID: roomID)) } + case .globalSearch: + presentGlobalSearch() case .accountProvisioningLink, .settings, .chatBackupSettings, .call, .genericCallLink: break // These routes cannot be handled. } @@ -422,8 +424,6 @@ class ChatsTabFlowCoordinator: FlowCoordinatorProtocol { stateMachine.processEvent(.startEncryptionResetFlow) case .presentStartChatScreen: stateMachine.processEvent(.startStartChatFlow) - case .presentGlobalSearch: - presentGlobalSearch() case .logout: actionsSubject.send(.logout) case .presentDeclineAndBlock(let userID, let roomID): diff --git a/ElementX/Sources/FlowCoordinators/EncryptionSettingsFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/EncryptionSettingsFlowCoordinator.swift index 3dae9e69a..ec2b47f96 100644 --- a/ElementX/Sources/FlowCoordinators/EncryptionSettingsFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/EncryptionSettingsFlowCoordinator.swift @@ -83,7 +83,8 @@ class EncryptionSettingsFlowCoordinator: FlowCoordinatorProtocol { case .roomList, .room, .roomAlias, .childRoom, .childRoomAlias, .roomDetails, .roomMemberDetails, .userProfile, .thread, .event, .eventOnRoomAlias, .childEvent, .childEventOnRoomAlias, - .call, .genericCallLink, .settings, .share, .transferOwnership: + .call, .genericCallLink, .settings, .share, .transferOwnership, + .globalSearch: // These routes aren't in this flow so clear the entire stack. clearRoute(animated: animated) case .chatBackupSettings: diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index c104922ff..01d862d6b 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -199,7 +199,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { } case .roomAlias, .childRoomAlias, .eventOnRoomAlias, .childEventOnRoomAlias: break // These are converted to a room ID route one level above. - case .accountProvisioningLink, .roomList, .userProfile, .call, .genericCallLink, .settings, .chatBackupSettings: + case .accountProvisioningLink, .roomList, .userProfile, .call, .genericCallLink, .settings, .chatBackupSettings, .globalSearch: break // These routes can't be handled. case .transferOwnership(let roomID): guard self.roomID == roomID else { fatalError("Navigation route doesn't belong to this room flow.") } diff --git a/ElementX/Sources/FlowCoordinators/RoomMembersFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomMembersFlowCoordinator.swift index 15379b2ac..45f2200f8 100644 --- a/ElementX/Sources/FlowCoordinators/RoomMembersFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomMembersFlowCoordinator.swift @@ -120,7 +120,7 @@ final class RoomMembersFlowCoordinator: FlowCoordinatorProtocol { break // These are converted to a room ID route one level above. case .accountProvisioningLink, .roomList, .room, .roomDetails, .event, .userProfile, .call, .genericCallLink, .settings, .chatBackupSettings, - .share, .transferOwnership, .thread: + .share, .transferOwnership, .thread, .globalSearch: break } } diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index ba6d19626..cd20adda4 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -137,7 +137,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { case .roomList, .room, .roomAlias, .childRoom, .childRoomAlias, .roomDetails, .roomMemberDetails, .userProfile, .event, .eventOnRoomAlias, .childEvent, .childEventOnRoomAlias, - .share, .transferOwnership, .thread: + .share, .transferOwnership, .thread, .globalSearch: clearPresentedSheets(animated: animated) // Make sure the presented route is visible. chatsTabFlowCoordinator.handleAppRoute(appRoute, animated: animated) if navigationTabCoordinator.selectedTab != .chats { diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift index ddaf642b4..512cbf803 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift @@ -34,7 +34,6 @@ enum HomeScreenCoordinatorAction { case presentRecoveryKeyScreen case presentEncryptionResetScreen case presentStartChatScreen - case presentGlobalSearch case logout } @@ -90,8 +89,6 @@ final class HomeScreenCoordinator: CoordinatorProtocol { actionsSubject.send(.presentEncryptionResetScreen) case .presentStartChatScreen: actionsSubject.send(.presentStartChatScreen) - case .presentGlobalSearch: - actionsSubject.send(.presentGlobalSearch) case .logout: actionsSubject.send(.logout) case .transferOwnership(let roomIdentifier): diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift index 4cc096150..16d9d6f36 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift @@ -25,7 +25,6 @@ enum HomeScreenViewModelAction { case presentSettingsScreen case presentFeedbackScreen case presentStartChatScreen - case presentGlobalSearch case logout } @@ -44,7 +43,6 @@ enum HomeScreenViewAction { case skipRecoveryKeyConfirmation case dismissNewSoundBanner case updateVisibleItemRange(Range) - case globalSearch case spaceFilters case markRoomAsUnread(roomIdentifier: String) case markRoomAsRead(roomIdentifier: String) diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index 5e388a5ee..f6f09a9aa 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -199,8 +199,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol roomSummaryProvider?.updateVisibleRange(range) case .startChat: actionsSubject.send(.presentStartChatScreen) - case .globalSearch: - actionsSubject.send(.presentGlobalSearch) case .spaceFilters: if spaceFilterSubject.value != nil { spaceFilterSubject.send(nil) diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenContent.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenContent.swift index 0fbb6ba58..54897b764 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenContent.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenContent.swift @@ -99,12 +99,6 @@ struct HomeScreenContent: View { scrollView.setContentOffset(oldOffset, animated: false) } } - .background { - Button("") { - context.send(viewAction: .globalSearch) - } - .keyboardShortcut(KeyEquivalent("k"), modifiers: [.command]) - } .overlay { if context.viewState.shouldShowEmptyFilterState { RoomListFiltersEmptyStateView(state: context.filtersState) diff --git a/UnitTests/Sources/HomeScreenViewModelTests.swift b/UnitTests/Sources/HomeScreenViewModelTests.swift index 6337051ef..098bfef53 100644 --- a/UnitTests/Sources/HomeScreenViewModelTests.swift +++ b/UnitTests/Sources/HomeScreenViewModelTests.swift @@ -488,8 +488,6 @@ extension HomeScreenViewModelAction: @retroactive Equatable { true case (.presentStartChatScreen, .presentStartChatScreen): true - case (.presentGlobalSearch, .presentGlobalSearch): - true case (.logout, .logout): true default: