Skip to content

Commit 8d709ef

Browse files
committed
Refactor RoomView to use HomeViewModel and remove RoomViewModel
RoomView now relies on HomeViewModel via environment object, removing the dedicated RoomViewModel. Entity filtering, hiding, and editing logic is integrated directly into RoomView, and a new edit sheet for managing visible/hidden entities is added. Project file and references are updated to remove RoomViewModel and ensure RoomView is properly included.
1 parent 6759350 commit 8d709ef

File tree

4 files changed

+194
-144
lines changed

4 files changed

+194
-144
lines changed

HomeAssistant.xcodeproj/project.pbxproj

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -648,8 +648,6 @@
648648
42333ADB2D0B1771001E8408 /* EntityRegistryListForDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42333ADA2D0B1771001E8408 /* EntityRegistryListForDisplay.swift */; };
649649
42333ADC2D0B1771001E8408 /* EntityRegistryListForDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42333ADA2D0B1771001E8408 /* EntityRegistryListForDisplay.swift */; };
650650
423622A02F05587800391BD0 /* EntityIconColorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4236229F2F05587800391BD0 /* EntityIconColorProvider.swift */; };
651-
423622A32F0560A600391BD0 /* RoomViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 423622A22F0560A600391BD0 /* RoomViewModel.swift */; };
652-
423622A52F0560BA00391BD0 /* RoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 423622A42F0560BA00391BD0 /* RoomView.swift */; };
653651
4237E6392E5333370023B673 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 4237E6382E5333370023B673 /* ZIPFoundation */; };
654652
42383F702D9576F700C745F2 /* AppTriggerSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42383F6F2D9576F700C745F2 /* AppTriggerSource.swift */; };
655653
42383F712D9576F700C745F2 /* AppTriggerSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42383F6F2D9576F700C745F2 /* AppTriggerSource.swift */; };
@@ -992,6 +990,7 @@
992990
42C60FA42F08131B0071A6F6 /* CoverControlIntents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42C60FA32F08131B0071A6F6 /* CoverControlIntents.swift */; };
993991
42C60FA62F081DA90071A6F6 /* DeviceClassProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42C60FA52F081DA90071A6F6 /* DeviceClassProvider.swift */; };
994992
42C60FA72F081DA90071A6F6 /* DeviceClassProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42C60FA52F081DA90071A6F6 /* DeviceClassProvider.swift */; };
993+
42C60FA92F0829B50071A6F6 /* RoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42C60FA82F0829B50071A6F6 /* RoomView.swift */; };
995994
42CB330D2DAE4FD800491DCE /* ServerSelectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42CB330C2DAE4FD800491DCE /* ServerSelectView.swift */; };
996995
42CB330F2DAE530400491DCE /* SettingsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42CB330E2DAE530400491DCE /* SettingsButton.swift */; };
997996
42CE8FA72B45D1E900C707F9 /* CoreStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42CE8FA52B45D1E900C707F9 /* CoreStrings.swift */; };
@@ -2259,8 +2258,6 @@
22592258
4231797F2D54FADD0037A8A4 /* AppIntentHaptics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntentHaptics.swift; sourceTree = "<group>"; };
22602259
42333ADA2D0B1771001E8408 /* EntityRegistryListForDisplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityRegistryListForDisplay.swift; sourceTree = "<group>"; };
22612260
4236229F2F05587800391BD0 /* EntityIconColorProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityIconColorProvider.swift; sourceTree = "<group>"; };
2262-
423622A22F0560A600391BD0 /* RoomViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomViewModel.swift; sourceTree = "<group>"; };
2263-
423622A42F0560BA00391BD0 /* RoomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomView.swift; sourceTree = "<group>"; };
22642261
42383F6F2D9576F700C745F2 /* AppTriggerSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTriggerSource.swift; sourceTree = "<group>"; };
22652262
4238DCA32DD1F1E300126434 /* AppSessionValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSessionValues.swift; sourceTree = "<group>"; };
22662263
4238E8522EB0D41200BDF010 /* ConnectionSecurityLevelBlockView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionSecurityLevelBlockView.swift; sourceTree = "<group>"; };
@@ -2743,6 +2740,7 @@
27432740
42C60FA12F0811AE0071A6F6 /* SwitchControlIntents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchControlIntents.swift; sourceTree = "<group>"; };
27442741
42C60FA32F08131B0071A6F6 /* CoverControlIntents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverControlIntents.swift; sourceTree = "<group>"; };
27452742
42C60FA52F081DA90071A6F6 /* DeviceClassProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceClassProvider.swift; sourceTree = "<group>"; };
2743+
42C60FA82F0829B50071A6F6 /* RoomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomView.swift; sourceTree = "<group>"; };
27462744
42CA28AD2B101D4D0093B31A /* HACornerRadius.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HACornerRadius.swift; sourceTree = "<group>"; };
27472745
42CA28AF2B101D6B0093B31A /* CardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardView.swift; sourceTree = "<group>"; };
27482746
42CA28B52B1022680093B31A /* HAButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HAButton.swift; sourceTree = "<group>"; };
@@ -5616,8 +5614,7 @@
56165614
42B1294E2F05684B00B957AF /* Room */ = {
56175615
isa = PBXGroup;
56185616
children = (
5619-
423622A22F0560A600391BD0 /* RoomViewModel.swift */,
5620-
423622A42F0560BA00391BD0 /* RoomView.swift */,
5617+
42C60FA82F0829B50071A6F6 /* RoomView.swift */,
56215618
);
56225619
path = Room;
56235620
sourceTree = "<group>";
@@ -8847,6 +8844,7 @@
88478844
423B5E0D2D677BB90000CB95 /* WidgetContentMargin.swift in Sources */,
88488845
42A32F382EEA393700B323F1 /* CarPlayLockConfirmation.swift in Sources */,
88498846
42462E782DA5421D00ECC8A7 /* CornerRadiusSizes.swift in Sources */,
8847+
42C60FA92F0829B50071A6F6 /* RoomView.swift in Sources */,
88508848
4240DF472E1D148F00FB0DE6 /* DeviceNameView.swift in Sources */,
88518849
420E2AE62C474710004921D8 /* WidgetBasicButtonView.swift in Sources */,
88528850
11A48D7F24CA7E820021BDD9 /* Action+Observation.swift in Sources */,
@@ -8887,7 +8885,6 @@
88878885
42F5C0952F02E5C100C50310 /* ControlOpenExperimentalDashboard.swift in Sources */,
88888886
3E4087ED2CE62B5A0085DF29 /* WidgetBasicViewProtocol.swift in Sources */,
88898887
422F88172D428E8300706A0A /* CustomWidgetActivateAppIntent.swift in Sources */,
8890-
423622A32F0560A600391BD0 /* RoomViewModel.swift in Sources */,
88918888
39A32EE22C0E384E00985722 /* UIImage+scaledToSize.swift in Sources */,
88928889
425573CC2B5574AD00145217 /* CarPlayAreasZonesTemplate+Build.swift in Sources */,
88938890
420C1BB22CF7DA9100AF22E7 /* ClientEventsLogView.swift in Sources */,
@@ -9188,7 +9185,6 @@
91889185
42B74A642D36B17000C37304 /* ReloadWidgetsAppIntent.swift in Sources */,
91899186
42FCD0152B9B29740057783F /* ThreadCredentialsManagementView.swift in Sources */,
91909187
42DC8B782E14027000D9999E /* SearchingServersAnimationView.swift in Sources */,
9191-
423622A52F0560BA00391BD0 /* RoomView.swift in Sources */,
91929188
42B980DE2DC3599300BC5C08 /* PrivacyView.swift in Sources */,
91939189
1168BF33271809C600DD4D15 /* OnboardingAuthError.swift in Sources */,
91949190
4238E8532EB0D41200BDF010 /* ConnectionSecurityLevelBlockView.swift in Sources */,

Sources/App/WebView/ExperimentalSpace/Home/HomeView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ struct HomeView: View {
4848
set: { selectedRoom = $0.map { ($0.id, $0.name) } }
4949
)) { room in
5050
RoomView(server: viewModel.server, roomId: room.id, roomName: room.name)
51+
.environmentObject(viewModel)
5152
.navigationTransition(.zoom(sourceID: selectedRoom?.id, in: roomNameSpace))
5253
.onDisappear {
5354
Task {

Sources/App/WebView/ExperimentalSpace/Room/RoomView.swift

Lines changed: 189 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,120 +5,255 @@ import SwiftUI
55

66
@available(iOS 26.0, *)
77
struct RoomView: View {
8-
@StateObject private var viewModel: RoomViewModel
9-
@Environment(\.dismiss) private var dismiss
8+
let server: Server
9+
let roomId: String
10+
let roomName: String
1011

11-
init(server: Server, roomId: String, roomName: String) {
12-
_viewModel = StateObject(wrappedValue: RoomViewModel(
13-
server: server,
14-
roomId: roomId,
15-
roomName: roomName
16-
))
17-
}
12+
@EnvironmentObject private var viewModel: HomeViewModel
13+
@Environment(\.dismiss) private var dismiss
14+
@State private var showHidden = false
15+
@State private var showEditSheet = false
1816

1917
var body: some View {
2018
NavigationStack {
2119
contentView
22-
.navigationTitle(viewModel.roomName)
20+
.navigationTitle(roomName)
2321
.navigationBarTitleDisplayMode(.large)
2422
.toolbar {
23+
ToolbarItem(placement: .topBarTrailing) {
24+
Button {
25+
showEditSheet = true
26+
} label: {
27+
Text("Edit")
28+
}
29+
.buttonStyle(.plain)
30+
}
31+
2532
ToolbarItem(placement: .topBarTrailing) {
2633
Button {
2734
dismiss()
2835
} label: {
2936
Image(systemSymbol: .xmark)
30-
.fontWeight(.semibold)
3137
}
3238
.buttonStyle(.plain)
3339
}
3440
}
3541
.background(ModernAssistBackgroundView(theme: .homeAssistant))
3642
}
37-
.task {
38-
await viewModel.loadEntities()
43+
.animation(DesignSystem.Animation.default, value: showHidden)
44+
.sheet(isPresented: $showEditSheet) {
45+
editEntitiesSheet
3946
}
4047
}
4148

42-
// MARK: - Content Views
49+
// MARK: - Content View
4350

4451
private var contentView: some View {
45-
ZStack {
46-
if viewModel.isLoading {
47-
EntityDisplayComponents.loadingView
48-
} else if let errorMessage = viewModel.errorMessage {
49-
EntityDisplayComponents.errorView(errorMessage)
50-
} else if viewModel.allEntities.isEmpty {
51-
EntityDisplayComponents.emptyStateView(message: L10n.RoomView.EmptyState.noEntities)
52-
} else {
53-
entitiesListView
54-
}
55-
}
56-
.animation(DesignSystem.Animation.default, value: viewModel.isLoading)
57-
.animation(DesignSystem.Animation.default, value: viewModel.errorMessage)
58-
.animation(DesignSystem.Animation.default, value: viewModel.allEntities.isEmpty)
59-
.frame(maxWidth: .infinity, maxHeight: .infinity)
60-
}
61-
62-
private var entitiesListView: some View {
6352
ScrollView {
6453
LazyVStack(
6554
alignment: .leading,
6655
spacing: DesignSystem.Spaces.three
6756
) {
68-
// Group entities by visibility status (checking against user's hidden entities)
69-
let hiddenEntities = viewModel.allEntities.filter { viewModel.hiddenEntityIds.contains($0.entityId) }
70-
let visibleEntities = viewModel.allEntities.filter { !viewModel.hiddenEntityIds.contains($0.entityId) }
71-
57+
// Visible Entities Section
7258
if !visibleEntities.isEmpty {
73-
Section {
74-
entityTilesGrid(for: visibleEntities)
75-
}
59+
entityTilesGrid(for: visibleEntities, isHidden: false)
7660
}
7761

62+
// Show/Hide Hidden Entities Button
7863
if !hiddenEntities.isEmpty {
64+
Button {
65+
withAnimation(DesignSystem.Animation.default) {
66+
showHidden.toggle()
67+
}
68+
} label: {
69+
HStack {
70+
Image(systemSymbol: showHidden ? .eyeSlashFill : .eye)
71+
Text(showHidden ? "Hide hidden entities" : "Show hidden entities")
72+
}
73+
.font(.body.weight(.medium))
74+
.foregroundStyle(.secondary)
75+
.frame(maxWidth: .infinity)
76+
.padding(.vertical, DesignSystem.Spaces.two)
77+
.background(
78+
.regularMaterial,
79+
in: RoundedRectangle(cornerRadius: DesignSystem.CornerRadius.oneAndHalf)
80+
)
81+
}
82+
.buttonStyle(.plain)
83+
}
84+
85+
// Hidden Entities Section
86+
if showHidden, !hiddenEntities.isEmpty {
7987
Section {
8088
entityTilesGrid(for: hiddenEntities, isHidden: true)
8189
} header: {
8290
sectionHeader(L10n.RoomView.Section.hidden)
8391
}
8492
}
93+
94+
// Empty State
95+
if visibleEntities.isEmpty, hiddenEntities.isEmpty {
96+
EntityDisplayComponents.emptyStateView(message: L10n.RoomView.EmptyState.noEntities)
97+
.frame(maxWidth: .infinity, maxHeight: .infinity)
98+
.padding(.top, DesignSystem.Spaces.six)
99+
}
85100
}
86101
.padding()
87102
}
88-
.transition(.opacity.combined(with: .move(edge: .bottom)))
103+
}
104+
105+
// MARK: - Computed Properties
106+
107+
private var currentRoomSection: HomeViewModel.RoomSection? {
108+
viewModel.groupedEntities.first(where: { $0.id == roomId })
109+
}
110+
111+
private var roomEntityIds: Set<String> {
112+
// Get area entity IDs directly from the database
113+
do {
114+
let areas = try AppArea.fetchAreas(for: viewModel.server.identifier.rawValue)
115+
if let area = areas.first(where: { $0.id == roomId }) {
116+
return area.entities
117+
}
118+
} catch {
119+
Current.Log.error("Failed to fetch area entities: \(error.localizedDescription)")
120+
}
121+
return []
122+
}
123+
124+
private var allRoomEntities: [HAEntity] {
125+
// Get all entities from entity states that belong to this room
126+
viewModel.entityStates.values.filter { entity in
127+
roomEntityIds.contains(entity.entityId)
128+
}
129+
}
130+
131+
private var visibleEntities: [HAEntity] {
132+
allRoomEntities.filter { entity in
133+
!viewModel.hiddenEntityIds.contains(entity.entityId)
134+
}
135+
.sorted { $0.entityId < $1.entityId }
136+
}
137+
138+
private var hiddenEntities: [HAEntity] {
139+
allRoomEntities.filter { entity in
140+
viewModel.hiddenEntityIds.contains(entity.entityId)
141+
}
142+
.sorted { $0.entityId < $1.entityId }
89143
}
90144

91145
// MARK: - Component Views
92146

147+
@ViewBuilder
93148
private func sectionHeader(_ title: String) -> some View {
94-
Text(title)
95-
.font(.title3.bold())
96-
.frame(maxWidth: .infinity, alignment: .leading)
97-
.padding(.vertical, DesignSystem.Spaces.one)
149+
HStack {
150+
Text(title)
151+
.font(.title2.bold())
152+
.lineLimit(1)
153+
.truncationMode(.middle)
154+
Spacer()
155+
}
156+
.frame(maxWidth: .infinity, alignment: .leading)
157+
.padding(.top, DesignSystem.Spaces.one)
158+
.padding(.horizontal, DesignSystem.Spaces.one)
98159
}
99160

100-
private func entityTilesGrid(for entities: [HAEntity], isHidden: Bool = false) -> some View {
161+
@ViewBuilder
162+
private func entityTilesGrid(for entities: [HAEntity], isHidden: Bool) -> some View {
101163
EntityDisplayComponents.entityTilesGrid(
102164
entities: entities,
103-
server: viewModel.server,
165+
server: server,
104166
isHidden: isHidden
105167
) { entity in
106-
Group {
107-
if isHidden {
108-
Button {
109-
viewModel.unhideEntity(entity.entityId)
110-
} label: {
111-
Label(L10n.RoomView.ContextMenu.unhide, systemSymbol: .eye)
168+
if isHidden {
169+
Button {
170+
viewModel.unhideEntity(entity.entityId)
171+
} label: {
172+
Label(L10n.RoomView.ContextMenu.unhide, systemSymbol: .eye)
173+
}
174+
} else {
175+
Button(role: .destructive) {
176+
viewModel.hideEntity(entity.entityId)
177+
} label: {
178+
Label("Hide", systemSymbol: .eyeSlash)
179+
}
180+
}
181+
}
182+
}
183+
184+
// MARK: - Edit Sheet
185+
186+
private var editEntitiesSheet: some View {
187+
NavigationStack {
188+
List {
189+
if !visibleEntities.isEmpty {
190+
Section {
191+
ForEach(visibleEntities, id: \.entityId) { entity in
192+
entityRow(entity: entity, isHidden: false)
193+
}
194+
} header: {
195+
Text("Visible Entities")
196+
}
197+
}
198+
199+
if !hiddenEntities.isEmpty {
200+
Section {
201+
ForEach(hiddenEntities, id: \.entityId) { entity in
202+
entityRow(entity: entity, isHidden: true)
203+
}
204+
} header: {
205+
Text("Hidden Entities")
206+
}
207+
}
208+
}
209+
.navigationTitle("Edit Entities")
210+
.navigationBarTitleDisplayMode(.inline)
211+
.toolbar {
212+
ToolbarItem(placement: .topBarTrailing) {
213+
Button("Done") {
214+
showEditSheet = false
112215
}
216+
}
217+
}
218+
}
219+
}
220+
221+
@ViewBuilder
222+
private func entityRow(entity: HAEntity, isHidden: Bool) -> some View {
223+
HStack {
224+
VStack(alignment: .leading, spacing: DesignSystem.Spaces.half) {
225+
Text(entity.attributes.friendlyName ?? entity.entityId)
226+
.font(.body)
227+
Text(entity.entityId)
228+
.font(.caption)
229+
.foregroundStyle(.secondary)
230+
}
231+
232+
Spacer()
233+
234+
Button {
235+
if isHidden {
236+
viewModel.unhideEntity(entity.entityId)
113237
} else {
114-
EmptyView()
238+
viewModel.hideEntity(entity.entityId)
115239
}
240+
} label: {
241+
Image(systemSymbol: isHidden ? .eye : .eyeSlash)
242+
.foregroundStyle(isHidden ? .blue : .secondary)
243+
.font(.title3)
116244
}
245+
.buttonStyle(.plain)
117246
}
247+
.opacity(isHidden ? 0.6 : 1.0)
118248
}
119249
}
120250

121251
@available(iOS 26.0, *)
122252
#Preview {
123-
RoomView(server: ServerFixture.standard, roomId: "living_room", roomName: "Living Room")
253+
RoomView(
254+
server: ServerFixture.standard,
255+
roomId: "room_1",
256+
roomName: "Living Room"
257+
)
258+
.environmentObject(HomeViewModel(server: ServerFixture.standard))
124259
}

0 commit comments

Comments
 (0)