Skip to content

Add search entities gesture shortcut #3535

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions HomeAssistant.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,10 @@
429106872BA9D22500D452F9 /* AudioRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 429106862BA9D22500D452F9 /* AudioRecorder.swift */; };
429106892BA9D5F700D452F9 /* AssistView+Build.swift in Sources */ = {isa = PBXBuildFile; fileRef = 429106882BA9D5F700D452F9 /* AssistView+Build.swift */; };
4291068C2BA9D79500D452F9 /* AudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4291068B2BA9D79500D452F9 /* AudioPlayer.swift */; };
429481E92DA93F4300A8B468 /* WebViewJavascriptCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 429481E82DA93F4300A8B468 /* WebViewJavascriptCommands.swift */; };
429481EB2DA93FA000A8B468 /* WebViewJavascriptCommandsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 429481EA2DA93FA000A8B468 /* WebViewJavascriptCommandsTests.swift */; };
429481ED2DA943E700A8B468 /* ListPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 429481EC2DA943E700A8B468 /* ListPicker.swift */; };
429481EF2DA94B9900A8B468 /* ListPickerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 429481EE2DA94B9900A8B468 /* ListPickerTests.swift */; };
4296C36D2B90DB640051B63C /* IntentActionAppEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4296C36B2B90DB630051B63C /* IntentActionAppEntity.swift */; };
4296C36E2B90DB640051B63C /* PerformAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4296C36C2B90DB630051B63C /* PerformAction.swift */; };
4296C3762B91F0F50051B63C /* WidgetActionsAppIntentTimelineProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4296C3742B91F0860051B63C /* WidgetActionsAppIntentTimelineProvider.swift */; };
Expand Down Expand Up @@ -2187,6 +2191,10 @@
429106882BA9D5F700D452F9 /* AssistView+Build.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AssistView+Build.swift"; sourceTree = "<group>"; };
4291068B2BA9D79500D452F9 /* AudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayer.swift; sourceTree = "<group>"; };
4291068D2BA9D93500D452F9 /* AssistService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssistService.swift; sourceTree = "<group>"; };
429481E82DA93F4300A8B468 /* WebViewJavascriptCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewJavascriptCommands.swift; sourceTree = "<group>"; };
429481EA2DA93FA000A8B468 /* WebViewJavascriptCommandsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewJavascriptCommandsTests.swift; sourceTree = "<group>"; };
429481EC2DA943E700A8B468 /* ListPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListPicker.swift; sourceTree = "<group>"; };
429481EE2DA94B9900A8B468 /* ListPickerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListPickerTests.swift; sourceTree = "<group>"; };
4296C36B2B90DB630051B63C /* IntentActionAppEntity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntentActionAppEntity.swift; sourceTree = "<group>"; };
4296C36C2B90DB630051B63C /* PerformAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PerformAction.swift; sourceTree = "<group>"; };
4296C3742B91F0860051B63C /* WidgetActionsAppIntentTimelineProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetActionsAppIntentTimelineProvider.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3343,6 +3351,7 @@
113FB1122515A065000AC680 /* ScaleFactorMutator.swift */,
11DE822D24FAC51000E636B8 /* IncomingURLHandler.swift */,
B64BB3A71E9C6551001E8B46 /* WebViewController.swift */,
429481E82DA93F4300A8B468 /* WebViewJavascriptCommands.swift */,
429BEA192D102F3A00F070F9 /* ConnectionErrorDetailsView.swift */,
420C57C62D0A6DE700D2D9AC /* NoActiveURLView.swift */,
42A47A842C45218D00C9B43D /* WebViewExternalMessageHandler.swift */,
Expand Down Expand Up @@ -3773,6 +3782,7 @@
42A47A882C452D7C00C9B43D /* Mocks */,
42DD84182B14D83B00936F16 /* WebViewExternalBusMessageTests.swift */,
42A47A862C452D5400C9B43D /* WebViewExternalMessageHandlerTests.swift */,
429481EA2DA93FA000A8B468 /* WebViewJavascriptCommandsTests.swift */,
);
path = WebView;
sourceTree = "<group>";
Expand Down Expand Up @@ -4268,6 +4278,7 @@
4278CB802D01E61D00CFAAC9 /* GesturesSetupView.swift */,
4278CB842D01F0B200CFAAC9 /* GesturesSetupViewModel.swift */,
4278CB872D01F65300CFAAC9 /* AppleLikeListTopRowHeader.swift */,
429481EC2DA943E700A8B468 /* ListPicker.swift */,
);
path = Gestures;
sourceTree = "<group>";
Expand Down Expand Up @@ -4347,6 +4358,7 @@
428626022DA5CCAE00D58D13 /* CollapsibleViewTests.swift */,
428626032DA5CCAE00D58D13 /* ExternalLinkButtonTests.swift */,
428626042DA5CCAE00D58D13 /* HAButtonStylesTests.swift */,
429481EE2DA94B9900A8B468 /* ListPickerTests.swift */,
);
path = Components;
sourceTree = "<group>";
Expand Down Expand Up @@ -7428,6 +7440,7 @@
42D5ACCE2C636F2B00D9C4E2 /* WatchConfigurationViewModel.swift in Sources */,
117D8A0824A9347F00580913 /* UIColor+CSSRGB.swift in Sources */,
427FEE562D9D39A50047C00C /* OnboardingNavigationView.swift in Sources */,
429481E92DA93F4300A8B468 /* WebViewJavascriptCommands.swift in Sources */,
11F3D74C2495377B00C05BBA /* SensorListViewController.swift in Sources */,
42FCCFFA2B9B1C310057783F /* ThreadCredentialsSharingToKeychainViewModel.swift in Sources */,
B6617EED1CFE79AD004DEE6D /* NSURL+QueryDictionary.swift in Sources */,
Expand Down Expand Up @@ -7629,6 +7642,7 @@
4251AA992C6B9D4C004CCC9D /* MagicItemCustomizationView.swift in Sources */,
1185DFB2271FF53800ED7D9A /* OnboardingAuthStepDuplicate.swift in Sources */,
427FEE632D9EA1400047C00C /* OnboardingNavigationViewModel.swift in Sources */,
429481ED2DA943E700A8B468 /* ListPicker.swift in Sources */,
42F1DA5D2B4BF85F002729BC /* WindowScenesManager.swift in Sources */,
11EFCDDA24F5FE0600314D85 /* SceneActivity.swift in Sources */,
427FEE662D9EBC430047C00C /* OnboardingPermissionsNavigationView.swift in Sources */,
Expand Down Expand Up @@ -7689,6 +7703,7 @@
11ED43A027279AFA00B5FD45 /* OnboardingAuthLoginImpl.test.swift in Sources */,
11EFD3BE27253504000AF78B /* OnboardingAuthStepConnectivity.test.swift in Sources */,
42E3B8C02D8ACE0400F5D084 /* OptionalsDefaultTests.swift in Sources */,
429481EF2DA94B9900A8B468 /* ListPickerTests.swift in Sources */,
42FCD0032B9B1CB70057783F /* ThreadCredentialsSharingViewModel.test.swift in Sources */,
42FCD0042B9B1CB70057783F /* ThreadCredentialsSharing.test.swift in Sources */,
42196ADE2DA5B58200BD501E /* OnboardingNavigationTests.swift in Sources */,
Expand Down Expand Up @@ -7722,6 +7737,7 @@
11ED439C2726600000B5FD45 /* OnboardingAuthStepSensors.test.swift in Sources */,
42A47A8A2C452DB500C9B43D /* MockWebViewController.swift in Sources */,
11A71C7324A4FC8A00D9565F /* ZoneManagerEquatableRegion.test.swift in Sources */,
429481EB2DA93FA000A8B468 /* WebViewJavascriptCommandsTests.swift in Sources */,
119C786725CF845800D41734 /* LocalizedStrings.test.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
9 changes: 8 additions & 1 deletion Sources/App/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,13 @@
"gestures.value.option.servers_list" = "Servers list";
"gestures.value.option.show_settings" = "Open App settings";
"gestures.value.option.show_sidebar" = "Show sidebar";
"gestures.value.option.search_entities" = "Search entities";
"gestures.value.option.more_info.search_entities" = "Search entities";
"gestures.category.homeAssistant" = "Home Assistant";
"gestures.category.page" = "Navigation";
"gestures.category.servers" = "Servers";
"gestures.category.app" = "App";
"gestures.category.other" = "Other";
"grdb.config.migration_error.failed_access_grdb" = "Failed to access database (GRDB), error: %@";
"grdb.config.migration_error.failed_to_save" = "Failed to save new config, error: %@";
"ha_api.api_error.cant_build_url" = "Cant build API URL";
Expand Down Expand Up @@ -1201,4 +1208,4 @@ Home Assistant is free and open source home automation software with a focus on
"widgets.sensors.description" = "Display state of sensors";
"widgets.sensors.not_configured" = "No Sensors Configured";
"widgets.sensors.title" = "Sensors";
"yes_label" = "Yes";
"yes_label" = "Yes";
40 changes: 24 additions & 16 deletions Sources/App/Settings/Gestures/GesturesSetupView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,35 @@ struct GesturesSetupView: View {
AppGesture.allCases.sorted(by: { $0.setupScreenOrder < $1.setupScreenOrder }),
id: \.self
) { gesture in
Picker(gesture.localizedString, selection: .init(get: {
viewModel.selection(for: gesture)
}, set: { newValue in
viewModel.setSelection(for: gesture, newValue: newValue)
})) {
ForEach(HAGestureAction.allCases, id: \.self) { action in
makeRow(gestureAction: action)
}
}
.modify({ view in
if #available(iOS 16.0, *) {
view.pickerStyle(.navigationLink)
} else {
view.pickerStyle(.menu)
}
})
let selection = viewModel.selection(for: gesture)
ListPicker(
title: gesture.localizedString,
selection: .init(get: {
.init(id: selection.rawValue, title: selection.localizedString)
}, set: { selectedItem in
guard let newValue = HAGestureAction(rawValue: selectedItem.id) else {
return
}
viewModel.setSelection(for: gesture, newValue: newValue)
}),
content: gestureActionsPickerContent
)
}
}
}
}

private var gestureActionsPickerContent: ListPickerContent {
var sections: [ListPickerContent.Section] = []
for category in HAGestureActionCategory.allCases {
let items = HAGestureAction.allCases.filter({ $0.category == category }).map { action in
ListPickerContent.Item(id: action.rawValue, title: action.localizedString)
}
sections.append(.init(id: category.rawValue, title: category.localizedString, items: items))
}
return .init(sections: sections)
}

private func makeRow(gestureAction: HAGestureAction) -> some View {
Text(gestureAction.localizedString).tag(gestureAction)
}
Expand Down
129 changes: 129 additions & 0 deletions Sources/App/Settings/Gestures/ListPicker.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import SwiftUI

protocol ListPickerSelectable: Identifiable {
var id: String { get }
var text: String { get }
}

struct ListPickerContent {
let sections: [Section]

struct Section {
let id: String
let title: String
let items: [Item]
}

struct Item {
let id: String
let title: String
}
}

struct ListPicker: View {
let title: String
@Binding var selection: ListPickerContent.Item
let content: ListPickerContent

var body: some View {
NavigationLink {
ListPickerContentView(title: title, selection: $selection, content: content)
} label: {
HStack {
Text(title)
Text(selection.title)
.frame(maxWidth: .infinity, alignment: .trailing)
.foregroundColor(Color(uiColor: .secondaryLabel))
}
}
}
}

struct ListPickerContentView: View {
let title: String
@Binding var selection: ListPickerContent.Item
let content: ListPickerContent

var body: some View {
List {
ForEach(content.sections, id: \.id) { section in
Section(section.title) {
ForEach(section.items, id: \.id) { item in
Button {
selection = item
} label: {
HStack {
Text(item.title)
.frame(maxWidth: .infinity, alignment: .leading)
if selection.id == item.id {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
}
}
}
}
}
}
}
.navigationBarTitleDisplayMode(.inline)
.navigationTitle(title)
}
}

#Preview("List picker") {
ListPickerPreview.standard
}

#Preview("List picker content") {
ListPickerPreview.content
}

enum ListPickerPreview {
static var standard: some View {
NavigationView {
List {
ListPicker(
title: "Select Item",
selection: .constant(.init(id: "2", title: "aaaa")),
content: .init(sections: [
.init(id: "1", title: "Section 1", items: [
.init(id: "1", title: "Abc"),
.init(id: "2", title: "aaaa"),
.init(id: "3", title: "bbbb"),
.init(id: "4", title: "ccccc"),
]),
.init(id: "2", title: "Section 2", items: [
.init(id: "5", title: "Abc"),
.init(id: "6", title: "aaaa"),
.init(id: "7", title: "bbbb"),
.init(id: "8", title: "ccccc"),
]),
])
)
}
}
}

static var content: some View {
NavigationView {
ListPickerContentView(
title: "Title 1",
selection: .constant(.init(id: "2", title: "aaaa")),
content: .init(sections: [
.init(id: "1", title: "Section 1", items: [
.init(id: "1", title: "Abc"),
.init(id: "2", title: "aaaa"),
.init(id: "3", title: "bbbb"),
.init(id: "4", title: "ccccc"),
]),
.init(id: "2", title: "Section 2", items: [
.init(id: "5", title: "Abc"),
.init(id: "6", title: "aaaa"),
.init(id: "7", title: "bbbb"),
.init(id: "8", title: "ccccc"),
]),
])
)
}
}
}
12 changes: 12 additions & 0 deletions Sources/App/WebView/WebViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1322,6 +1322,18 @@ extension WebViewController {
break
case .openDebug:
openDebug()
case .searchEntities:
showSearchEntities()
}
}

private func showSearchEntities() {
webView.evaluateJavaScript(WebViewJavascriptCommands.searchEntitiesKeyEvent) { _, error in
if let error {
Current.Log.error("JavaScript error while trying to open entities search: \(error)")
} else {
Current.Log.info("Open entities search command sent to webview")
}
}
}

Expand Down
15 changes: 15 additions & 0 deletions Sources/App/WebView/WebViewJavascriptCommands.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation

enum WebViewJavascriptCommands {
static var searchEntitiesKeyEvent = """
var event = new KeyboardEvent('keydown', {
key: 'e',
code: 'KeyE',
keyCode: 69,
which: 69,
bubbles: true,
cancelable: true
});
document.dispatchEvent(event);
"""
}
Loading
Loading