iOS/Sources/App/WebView/ExperimentalSpace/EntityTile/HomeEntityTileView.swift
Bruno Pantaleão Gonçalves 9ee8ac4419
Add summaries and area context to native dashboard (#4221)
<!-- Thank you for submitting a Pull Request and helping to improve Home
Assistant. Please complete the following sections to help the processing
and review of your changes. Please do not delete anything from this
template. -->

## Summary
<!-- Provide a brief summary of the changes you have made and most
importantly what they aim to achieve -->

## Screenshots
<!-- If this is a user-facing change not in the frontend, please include
screenshots in light and dark mode. -->
<img width="1206" height="2622" alt="Simulator Screenshot - iPhone 17 -
2026-01-14 at 16 42 40"
src="https://github.com/user-attachments/assets/0e023ce2-f767-40a8-900f-1b1a00a127f5"
/>

## Link to pull request in Documentation repository
<!-- Pull requests that add, change or remove functionality must have a
corresponding pull request in the Companion App Documentation repository
(https://github.com/home-assistant/companion.home-assistant). Please add
the number of this pull request after the "#" -->
Documentation: home-assistant/companion.home-assistant#

## Any other notes
<!-- If there is any other information of note, like if this Pull
Request is part of a bigger change, please include it here. -->
2026-01-14 16:39:46 +00:00

131 lines
3.8 KiB
Swift

import AppIntents
import HAKit
import Shared
import SwiftUI
/// Entity tile view specifically designed for use in HomeView
/// Handles business logic like device class lookup, icon color computation,
/// app intents integration, and more info dialog presentation
@available(iOS 26.0, *)
struct HomeEntityTileView: View {
let server: Server
let haEntity: HAEntity
let areaName: String?
@Namespace private var namespace
@State private var iconColor: Color = .secondary
@State private var showMoreInfoDialog = false
@State private var deviceClass: DeviceClass = .unknown
init(server: Server, haEntity: HAEntity, areaName: String? = nil) {
self.server = server
self.haEntity = haEntity
self.areaName = areaName
}
var body: some View {
EntityTileView(
entityName: entityName,
entityState: entityState,
icon: icon,
iconColor: iconColor,
isUnavailable: isUnavailable,
onIconTap: handleIconTap,
onTileTap: handleTileTap
)
.onChange(of: haEntity) { _, _ in
updateIconColor()
}
.onAppear {
getDeviceClass()
updateIconColor()
}
.matchedTransitionSource(id: haEntity.entityId, in: namespace)
.fullScreenCover(isPresented: $showMoreInfoDialog) {
EntityMoreInfoDialogView(
server: server,
haEntity: haEntity
)
.navigationTransition(.zoom(sourceID: haEntity.entityId, in: namespace))
}
}
// MARK: - Computed Properties
private var entityName: String {
haEntity.attributes.friendlyName ?? haEntity.entityId
}
private var entityState: String {
let state = Domain(entityId: haEntity.entityId)?.contextualStateDescription(for: haEntity) ?? haEntity.state
if let areaName {
return "\(state) · \(areaName)"
}
return state
}
private var icon: MaterialDesignIcons {
if let entityIcon = haEntity.attributes.icon {
return MaterialDesignIcons(serversideValueNamed: entityIcon)
} else if let domain = Domain(entityId: haEntity.entityId) {
let stateString = haEntity.state
let domainState = Domain.State(rawValue: stateString) ?? .unknown
return domain.icon(deviceClass: deviceClass.rawValue, state: domainState)
} else {
return .homeIcon
}
}
private var isUnavailable: Bool {
let state = haEntity.state.lowercased()
return [Domain.State.unavailable.rawValue, Domain.State.unknown.rawValue].contains(state)
}
// MARK: - Actions
private func handleIconTap() {
#if os(iOS)
// Execute the app intent for the entity
let intent = AppIntentProvider.intent(for: haEntity, server: server)
Task {
_ = try? await intent.perform()
}
#endif
}
private func handleTileTap() {
showMoreInfoDialog = true
}
// MARK: - Business Logic
private func getDeviceClass() {
deviceClass = DeviceClassProvider.deviceClass(
for: haEntity.entityId,
serverId: server.identifier.rawValue
)
}
private func updateIconColor() {
let state = haEntity.state
let colorMode = haEntity.attributes["color_mode"] as? String
let rgbColor = haEntity.attributes["rgb_color"] as? [Int]
let hsColor = haEntity.attributes["hs_color"] as? [Double]
if isUnavailable {
iconColor = .gray
return
}
iconColor = EntityIconColorProvider.iconColor(
domain: Domain(entityId: haEntity.entityId) ?? .switch,
state: state,
colorMode: colorMode,
rgbColor: rgbColor,
hsColor: hsColor
)
}
}