mirror of
https://github.com/home-assistant/iOS.git
synced 2026-04-13 01:12:53 -05:00
<!-- 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 --> First iteration of the widget builder, user is able to choose what entities to display, choose a display text for them, color customization and "on tap" action. ## Screenshots <!-- If this is a user-facing change not in the frontend, please include screenshots in light and dark mode. --> https://github.com/user-attachments/assets/5949322a-a8df-4b3d-aaab-d4ba3853cc84 ## 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. -->
132 lines
4.5 KiB
Swift
132 lines
4.5 KiB
Swift
import Foundation
|
|
import GRDB
|
|
import HAKit
|
|
|
|
public final class ControlEntityProvider {
|
|
public enum States: String {
|
|
case open
|
|
case opening
|
|
case close
|
|
case closing
|
|
case on
|
|
case off
|
|
}
|
|
|
|
public struct State {
|
|
public let value: String
|
|
public let unitOfMeasurement: String?
|
|
public let domainState: Domain.State?
|
|
|
|
public init(value: String, unitOfMeasurement: String?, domainState: Domain.State?) {
|
|
self.value = value
|
|
self.unitOfMeasurement = unitOfMeasurement
|
|
self.domainState = domainState
|
|
}
|
|
}
|
|
|
|
public let domains: [Domain]
|
|
|
|
public init(domains: [Domain]) {
|
|
self.domains = domains
|
|
}
|
|
|
|
public func currentState(serverId: String, entityId: String) async throws -> String? {
|
|
guard let server = Current.servers.all.first(where: { $0.identifier.rawValue == serverId }),
|
|
let connection = Current.api(for: server)?.connection else {
|
|
return nil
|
|
}
|
|
let state: String? = await withCheckedContinuation { continuation in
|
|
connection.send(.init(
|
|
type: .rest(.get, "states/\(entityId)")
|
|
)) { result in
|
|
switch result {
|
|
case let .success(data):
|
|
let state: String? = data.decode("state", fallback: nil)
|
|
continuation.resume(returning: state)
|
|
case let .failure(error):
|
|
Current.Log.error("Failed to get \(entityId) state for ControlEntityProvider: \(error)")
|
|
continuation.resume(returning: nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
return state
|
|
}
|
|
|
|
public func getEntities(matching string: String? = nil) -> [(Server, [HAAppEntity])] {
|
|
var entitiesPerServer: [(Server, [HAAppEntity])] = []
|
|
for server in Current.servers.all.sorted(by: { $0.info.name < $1.info.name }) {
|
|
do {
|
|
var entities: [HAAppEntity] = try Current.database.read { db in
|
|
try HAAppEntity
|
|
.filter(Column(DatabaseTables.AppEntity.serverId.rawValue) == server.identifier.rawValue)
|
|
.filter(domains.map(\.rawValue).contains(Column(DatabaseTables.AppEntity.domain.rawValue)))
|
|
.fetchAll(db)
|
|
}
|
|
if let string {
|
|
entities = entities.filter({ entity in
|
|
entity.name.lowercased().contains(string.lowercased())
|
|
})
|
|
}
|
|
entitiesPerServer.append((server, entities))
|
|
} catch {
|
|
Current.Log.error("Failed to load entities from database: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
return entitiesPerServer
|
|
}
|
|
|
|
public func state(server: Server, entityId: String) async -> State? {
|
|
guard let connection = Current.api(for: server)?.connection else {
|
|
Current.Log.error("No API available to fetch state data")
|
|
return nil
|
|
}
|
|
|
|
let result = await withCheckedContinuation { continuation in
|
|
connection.send(.init(
|
|
type: .rest(.get, "states/\(entityId)"),
|
|
shouldRetry: true
|
|
)) { result in
|
|
continuation.resume(returning: result)
|
|
}
|
|
}
|
|
|
|
var data: HAData?
|
|
switch result {
|
|
case let .success(resultData):
|
|
data = resultData
|
|
case let .failure(error):
|
|
Current.Log.error("Failed to get state: \(error)")
|
|
return nil
|
|
}
|
|
|
|
guard let data else {
|
|
return nil
|
|
}
|
|
|
|
var state: [String: Any]?
|
|
switch data {
|
|
case let .dictionary(response):
|
|
state = response
|
|
default:
|
|
Current.Log.error("Failed to get state bad response data")
|
|
return nil
|
|
}
|
|
|
|
var stateValue = (state?["state"] as? String) ?? "N/A"
|
|
stateValue = StatePrecision.adjustPrecision(
|
|
serverId: server.identifier.rawValue,
|
|
entityId: entityId,
|
|
stateValue: stateValue
|
|
)
|
|
let unitOfMeasurement = (state?["attributes"] as? [String: Any])?["unit_of_measurement"] as? String
|
|
stateValue = stateValue.capitalizedFirst
|
|
return .init(
|
|
value: stateValue,
|
|
unitOfMeasurement: unitOfMeasurement,
|
|
domainState: .init(rawValue: stateValue.trimmingCharacters(in: .whitespacesAndNewlines).lowercased())
|
|
)
|
|
}
|
|
}
|