Skip to content

Commit

Permalink
Add servers as filter to pick entities in the EntityPicker flow (#3444)
Browse files Browse the repository at this point in the history
  • Loading branch information
bgoncal authored Feb 19, 2025
1 parent ded3413 commit 057c9d0
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 48 deletions.
8 changes: 8 additions & 0 deletions HomeAssistant.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,7 @@
42C1012B2CD3DB8A0012BA78 /* CoverIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42C101292CD3DB8A0012BA78 /* CoverIntent.swift */; };
42C1012E2CD3DBF00012BA78 /* ControlCover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42C1012C2CD3DBF00012BA78 /* ControlCover.swift */; };
42C101302CD3DC0C0012BA78 /* ControlCoverValueProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42C1012F2CD3DC0C0012BA78 /* ControlCoverValueProvider.swift */; };
42C131D02D66084C00AF48E6 /* PillView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42C131CF2D66084C00AF48E6 /* PillView.swift */; };
42C3737F2BC415AC00898990 /* UIViewController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42C3737E2BC415AC00898990 /* UIViewController+Extensions.swift */; };
42C373B22BC5382900898990 /* HostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42C373B12BC5382900898990 /* HostingController.swift */; };
42CE8FA72B45D1E900C707F9 /* CoreStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42CE8FA52B45D1E900C707F9 /* CoreStrings.swift */; };
Expand Down Expand Up @@ -856,6 +857,7 @@
42EFFAEC2C8882DD002F10FC /* CarPlayConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42EFFAEB2C8882DD002F10FC /* CarPlayConfigurationView.swift */; };
42F158462CA15C99009C7201 /* ControlSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42F158452CA15C99009C7201 /* ControlSwitch.swift */; };
42F158482CA15FA7009C7201 /* SwitchIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42F158472CA15FA7009C7201 /* SwitchIntent.swift */; };
42F161442D661D11003DDC75 /* ServersPickerPillList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42F161422D661CBA003DDC75 /* ServersPickerPillList.swift */; };
42F1DA5B2B4BF7DF002729BC /* WindowSizeObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42F1DA5A2B4BF7DF002729BC /* WindowSizeObserver.swift */; };
42F1DA5D2B4BF85F002729BC /* WindowScenesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42F1DA5C2B4BF85F002729BC /* WindowScenesManager.swift */; };
42F1DA5F2B4D4B32002729BC /* CarPlayServerListTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42F1DA5E2B4D4B32002729BC /* CarPlayServerListTemplate.swift */; };
Expand Down Expand Up @@ -2137,6 +2139,7 @@
42C101292CD3DB8A0012BA78 /* CoverIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverIntent.swift; sourceTree = "<group>"; };
42C1012C2CD3DBF00012BA78 /* ControlCover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlCover.swift; sourceTree = "<group>"; };
42C1012F2CD3DC0C0012BA78 /* ControlCoverValueProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlCoverValueProvider.swift; sourceTree = "<group>"; };
42C131CF2D66084C00AF48E6 /* PillView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillView.swift; sourceTree = "<group>"; };
42C3737E2BC415AC00898990 /* UIViewController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extensions.swift"; sourceTree = "<group>"; };
42C373AF2BC536AA00898990 /* WatchApp-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WatchApp-Bridging-Header.h"; sourceTree = "<group>"; };
42C373B12BC5382900898990 /* HostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostingController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2188,6 +2191,7 @@
42EFFAEB2C8882DD002F10FC /* CarPlayConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarPlayConfigurationView.swift; sourceTree = "<group>"; };
42F158452CA15C99009C7201 /* ControlSwitch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlSwitch.swift; sourceTree = "<group>"; };
42F158472CA15FA7009C7201 /* SwitchIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchIntent.swift; sourceTree = "<group>"; };
42F161422D661CBA003DDC75 /* ServersPickerPillList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServersPickerPillList.swift; sourceTree = "<group>"; };
42F1DA5A2B4BF7DF002729BC /* WindowSizeObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowSizeObserver.swift; sourceTree = "<group>"; };
42F1DA5C2B4BF85F002729BC /* WindowScenesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowScenesManager.swift; sourceTree = "<group>"; };
42F1DA5E2B4D4B32002729BC /* CarPlayServerListTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarPlayServerListTemplate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4298,13 +4302,15 @@
42CA28B12B101D9C0093B31A /* Components */ = {
isa = PBXGroup;
children = (
42F161422D661CBA003DDC75 /* ServersPickerPillList.swift */,
429BEA1B2D1030EA00F070F9 /* SheetCloseButton.swift */,
42FCD0052B9B1D9E0057783F /* CollapsibleView.swift */,
42CA28AF2B101D6B0093B31A /* CardView.swift */,
42CA28B52B1022680093B31A /* HAButton.swift */,
42790C452C4808FA00E31B38 /* AppleLikeBottomSheet.swift */,
4254C4CC2D103F7B00245021 /* ExternalLinkButton.swift */,
42B74A5C2D36A47E00C37304 /* CloseButton.swift */,
42C131CF2D66084C00AF48E6 /* PillView.swift */,
);
path = Components;
sourceTree = "<group>";
Expand Down Expand Up @@ -7571,6 +7577,7 @@
118F046924CB895A00CBBD5C /* UIColor+CSS3+Hex.swift in Sources */,
1109F81F24A1C011002590F2 /* SensorProvider.swift in Sources */,
4254C4CA2D103ABB00245021 /* ExternalLink.swift in Sources */,
42F161442D661D11003DDC75 /* ServersPickerPillList.swift in Sources */,
4297ADA82C89C74A00790812 /* GRDB+Initialization.swift in Sources */,
1141182A24AFA10900E6525C /* WebhookResponseHandler.swift in Sources */,
420CFC792D3F9CAB009A94F3 /* AppEntityRegistryListForDisplayTable.swift in Sources */,
Expand Down Expand Up @@ -7761,6 +7768,7 @@
D05A4D32216DD206009FD1EB /* MJPEGStreamer.swift in Sources */,
426266452C11B02C0081A818 /* InteractiveImmediateMessages.swift in Sources */,
42CE8FB02B46C3D900C707F9 /* CoreStrings+Values.swift in Sources */,
42C131D02D66084C00AF48E6 /* PillView.swift in Sources */,
11C4628B24B1230E00031902 /* WebhookResponseServiceCall.swift in Sources */,
D0EEF30A214DD64C00D1D360 /* UIImage+Icons.swift in Sources */,
42B94BED2B96083C00DEE060 /* AssistModel.swift in Sources */,
Expand Down
19 changes: 8 additions & 11 deletions Sources/App/Settings/ClientEventsLogView/ClientEventsLogView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,21 @@ struct ClientEventsLogView: View {
Button {
viewModel.resetTypeFilter()
} label: {
filterPill(L10n.ClientEvents.EventType.all, selected: viewModel.typeFilter == nil)
PillView(
text: L10n.ClientEvents.EventType.all,
selected: viewModel.typeFilter == nil
)
}
ForEach(ClientEvent.EventType.allCases.sorted { e1, e2 in
e1.displayText < e2.displayText
}, id: \.self) { type in
Button {
viewModel.typeFilter = type
} label: {
filterPill(type.displayText, selected: viewModel.typeFilter == type)
PillView(
text: type.displayText,
selected: viewModel.typeFilter == type
)
}
}
}
Expand All @@ -124,15 +130,6 @@ struct ClientEventsLogView: View {
}
}

private func filterPill(_ text: String, selected: Bool) -> some View {
Text(text)
.foregroundStyle(selected ? .white : Color(uiColor: .label))
.padding(Spaces.one)
.padding(.horizontal)
.background(selected ? Color.asset(Asset.Colors.haPrimary) : Color.secondary.opacity(0.1))
.clipShape(Capsule())
}

private func listItem(_ event: ClientEvent) -> some View {
NavigationLink {
eventDescription(event)
Expand Down
12 changes: 8 additions & 4 deletions Sources/App/Settings/MagicItem/Add/MagicItemAddView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ struct MagicItemAddView: View {
.onAppear {
autoSelectItemType()
viewModel.loadContent()

if viewModel.selectedServerId == nil {
viewModel.selectedServerId = Current.servers.all.first?.identifier.rawValue
}
}
.toolbar(content: {
CloseButton {
Expand Down Expand Up @@ -159,10 +163,10 @@ struct MagicItemAddView: View {

@ViewBuilder
private var entitiesPerServerList: some View {
ForEach(Array(viewModel.entities.keys), id: \.identifier) { server in
Section(server.info.name) {
list(entities: viewModel.entities[server] ?? [], serverId: server.identifier.rawValue, type: .entity)
}
ServersPickerPillList(selectedServerId: $viewModel.selectedServerId)
if let server = Current.servers.all
.first(where: { $0.identifier.rawValue == viewModel.selectedServerId }) ?? Current.servers.all.first {
list(entities: viewModel.entities[server] ?? [], serverId: server.identifier.rawValue, type: .entity)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ final class MagicItemAddViewModel: ObservableObject {
@Published var entities: [Server: [HAAppEntity]] = [:]
@Published var actions: [Action] = []
@Published var searchText: String = ""
@Published var selectedServerId: String?

private var entitiesSubscription: AnyCancellable?

Expand Down
83 changes: 50 additions & 33 deletions Sources/App/Settings/MagicItem/EntityPicker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,21 @@ struct EntityPicker: View {
@State private var entities: [HAAppEntity] = []
@Binding private var selectedEntity: HAAppEntity?
@State private var searchTerm = ""
@State private var selectedServerId: String?

init(selectedEntity: Binding<HAAppEntity?>, domainFilter: Domain?) {
self.domainFilter = domainFilter
self._selectedEntity = selectedEntity
}

var body: some View {
button
.sheet(isPresented: $showList) {
screen
}
}

private var button: some View {
Button(action: {
showList = true
}, label: {
Expand All @@ -25,45 +33,54 @@ struct EntityPicker: View {
Text(L10n.EntityPicker.placeholder)
}
})
.sheet(isPresented: $showList) {
NavigationView {
List {
ForEach(Current.servers.all, id: \.identifier) { server in
Section(server.info.name) {
ForEach(entities.filter({ entity in
if searchTerm.count > 2 {
return entity.serverId == server.identifier.rawValue && (
entity.name.lowercased().contains(searchTerm.lowercased()) ||
entity.entityId.lowercased().contains(searchTerm.lowercased())
)
}

private var screen: some View {
NavigationView {
List {
ServersPickerPillList(selectedServerId: $selectedServerId)
ForEach(entities.filter({ entity in
if searchTerm.count > 2 {
return entity.serverId == selectedServerId && (
entity.name.lowercased().contains(searchTerm.lowercased()) ||
entity.entityId.lowercased().contains(searchTerm.lowercased())
)
} else {
return entity.serverId == selectedServerId
}
}), id: \.id) { entity in
Button(action: {
selectedEntity = entity
showList = false
}, label: {
VStack {
Group {
if let selectedEntity, selectedEntity == entity {
Label(entity.name, systemSymbol: .checkmark)
} else {
return entity.serverId == server.identifier.rawValue
Text(entity.name)
}
}), id: \.id) { entity in
Button(action: {
selectedEntity = entity
showList = false
}, label: {
if let selectedEntity, selectedEntity == entity {
Label(entity.name, systemSymbol: .checkmark)
} else {
Text(entity.name)
}
})
.tint(.accentColor)
Text(entity.entityId)
.font(.footnote)
.foregroundStyle(.secondary)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
})
.tint(.accentColor)
}
.searchable(text: $searchTerm)
.onAppear {
fetchEntities()
}
.searchable(text: $searchTerm)
.onAppear {
fetchEntities()
if selectedServerId == nil {
selectedServerId = Current.servers.all.first?.identifier.rawValue
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
CloseButton {
showList = false
}
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
CloseButton {
showList = false
}
}
}
Expand Down
28 changes: 28 additions & 0 deletions Sources/Shared/DesignSystem/Components/PillView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import SwiftUI

public struct PillView: View {
private let selected: Bool
private let text: String

public init(text: String, selected: Bool) {
self.text = text
self.selected = selected
}

public var body: some View {
Text(text)
.foregroundStyle(selected ? .white : Color(uiColor: .label))
.padding(Spaces.one)
.padding(.horizontal)
.background(selected ? Color.asset(Asset.Colors.haPrimary) : Color.secondary.opacity(0.1))
.clipShape(Capsule())
}
}

#Preview {
HStack {
PillView(text: "Value1", selected: true)
PillView(text: "Value2", selected: false)
PillView(text: "Value3", selected: false)
}
}
47 changes: 47 additions & 0 deletions Sources/Shared/DesignSystem/Components/ServersPickerPillList.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import SwiftUI

public struct ServersPickerPillList: View {
@Binding private var selectedServerId: String?

public init(selectedServerId: Binding<String?>) {
self._selectedServerId = selectedServerId
}

public var body: some View {
serversList
}

@ViewBuilder
private var serversList: some View {
if Current.servers.all.count > 1 {
Section {
ScrollView(.horizontal) {
HStack {
ForEach(Current.servers.all.sorted(by: { lhs, rhs in
lhs.info.sortOrder < rhs.info.sortOrder
}), id: \.identifier) { server in
Button {
selectedServerId = server.identifier.rawValue
} label: {
PillView(
text: server.info.name,
selected: selectedServerId == server.identifier.rawValue
)
}
}
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.listRowBackground(Color.clear)
.listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
}
}
}

#Preview {
List {
ServersPickerPillList(selectedServerId: .constant("1"))
}
}

0 comments on commit 057c9d0

Please sign in to comment.