mirror of
https://github.com/home-assistant/iOS.git
synced 2026-04-28 18:22:59 -05:00
Add servers as filter to pick entities in the EntityPicker flow (#3444)
This commit is contained in:
committed by
GitHub
parent
ded3413817
commit
057c9d0845
@@ -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 */; };
|
||||
@@ -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 */; };
|
||||
@@ -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>"; };
|
||||
@@ -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>"; };
|
||||
@@ -4298,6 +4302,7 @@
|
||||
42CA28B12B101D9C0093B31A /* Components */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
42F161422D661CBA003DDC75 /* ServersPickerPillList.swift */,
|
||||
429BEA1B2D1030EA00F070F9 /* SheetCloseButton.swift */,
|
||||
42FCD0052B9B1D9E0057783F /* CollapsibleView.swift */,
|
||||
42CA28AF2B101D6B0093B31A /* CardView.swift */,
|
||||
@@ -4305,6 +4310,7 @@
|
||||
42790C452C4808FA00E31B38 /* AppleLikeBottomSheet.swift */,
|
||||
4254C4CC2D103F7B00245021 /* ExternalLinkButton.swift */,
|
||||
42B74A5C2D36A47E00C37304 /* CloseButton.swift */,
|
||||
42C131CF2D66084C00AF48E6 /* PillView.swift */,
|
||||
);
|
||||
path = Components;
|
||||
sourceTree = "<group>";
|
||||
@@ -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 */,
|
||||
@@ -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 */,
|
||||
|
||||
@@ -90,7 +90,10 @@ 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
|
||||
@@ -98,7 +101,10 @@ struct ClientEventsLogView: View {
|
||||
Button {
|
||||
viewModel.typeFilter = type
|
||||
} label: {
|
||||
filterPill(type.displayText, selected: viewModel.typeFilter == type)
|
||||
PillView(
|
||||
text: type.displayText,
|
||||
selected: viewModel.typeFilter == type
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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?
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ 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
|
||||
@@ -16,6 +17,13 @@ struct EntityPicker: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
button
|
||||
.sheet(isPresented: $showList) {
|
||||
screen
|
||||
}
|
||||
}
|
||||
|
||||
private var button: some View {
|
||||
Button(action: {
|
||||
showList = true
|
||||
}, label: {
|
||||
@@ -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())
|
||||
)
|
||||
} else {
|
||||
return entity.serverId == server.identifier.rawValue
|
||||
}
|
||||
}), 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
.searchable(text: $searchTerm)
|
||||
.onAppear {
|
||||
fetchEntities()
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
CloseButton {
|
||||
showList = false
|
||||
}), id: \.id) { entity in
|
||||
Button(action: {
|
||||
selectedEntity = entity
|
||||
showList = false
|
||||
}, label: {
|
||||
VStack {
|
||||
Group {
|
||||
if let selectedEntity, selectedEntity == entity {
|
||||
Label(entity.name, systemSymbol: .checkmark)
|
||||
} else {
|
||||
Text(entity.name)
|
||||
}
|
||||
Text(entity.entityId)
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
})
|
||||
.tint(.accentColor)
|
||||
}
|
||||
}
|
||||
.searchable(text: $searchTerm)
|
||||
.onAppear {
|
||||
fetchEntities()
|
||||
if selectedServerId == nil {
|
||||
selectedServerId = Current.servers.all.first?.identifier.rawValue
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
CloseButton {
|
||||
showList = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
28
Sources/Shared/DesignSystem/Components/PillView.swift
Normal file
28
Sources/Shared/DesignSystem/Components/PillView.swift
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user