mirror of
https://github.com/home-assistant/iOS.git
synced 2026-05-02 14:54:21 -05:00
Use database areas instead of always fetching from API (#3967)
<!-- 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. --> ## 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. --> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
cee3842d85
commit
210c61ee63
@@ -9,6 +9,8 @@ final class WebViewSceneDelegate: NSObject, UIWindowSceneDelegate {
|
||||
var windowController: WebViewWindowController?
|
||||
var urlHandler: IncomingURLHandler?
|
||||
|
||||
private var updateDatabaseTask: Task<Void, Never>?
|
||||
|
||||
// swiftlint:disable cyclomatic_complexity
|
||||
func scene(
|
||||
_ scene: UIScene,
|
||||
@@ -120,7 +122,10 @@ final class WebViewSceneDelegate: NSObject, UIWindowSceneDelegate {
|
||||
}
|
||||
|
||||
func sceneDidBecomeActive(_ scene: UIScene) {
|
||||
updateDatabase()
|
||||
updateDatabaseTask?.cancel()
|
||||
updateDatabaseTask = Task {
|
||||
await updateDatabase()
|
||||
}
|
||||
cleanWidgetsCache()
|
||||
updateLocation()
|
||||
}
|
||||
@@ -168,13 +173,13 @@ final class WebViewSceneDelegate: NSObject, UIWindowSceneDelegate {
|
||||
}
|
||||
|
||||
/// Sets up model manager and update database tables for cached panels and entities
|
||||
private func updateDatabase() {
|
||||
private func updateDatabase() async {
|
||||
Current.modelManager.cleanup().cauterize()
|
||||
Current.modelManager.subscribe(isAppInForeground: {
|
||||
UIApplication.shared.applicationState == .active
|
||||
})
|
||||
|
||||
Current.appDatabaseUpdater.update()
|
||||
await Current.appDatabaseUpdater.update()
|
||||
Current.panelsUpdater.update()
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,6 @@ final class MagicItemAddViewModel: ObservableObject {
|
||||
@Published var searchText: String = ""
|
||||
@Published var selectedServerId: String?
|
||||
|
||||
/// [ServerId: [AreaId: Set<EntityId>]]
|
||||
@Published var serversAreasAndItsEntities: [String: [String: Set<String>]] = [:]
|
||||
|
||||
private var entitiesSubscription: AnyCancellable?
|
||||
|
||||
init() {
|
||||
@@ -55,19 +52,17 @@ final class MagicItemAddViewModel: ObservableObject {
|
||||
func loadContent() {
|
||||
loadAppEntities()
|
||||
loadActions()
|
||||
Task {
|
||||
await loadEntitiesForAreas()
|
||||
}
|
||||
}
|
||||
|
||||
func subtitleForEntity(entity: HAAppEntity, serverId: String) -> String {
|
||||
guard let areasAndItsEntities = serversAreasAndItsEntities[serverId] else {
|
||||
return ""
|
||||
}
|
||||
for (areaId, entityIds) in areasAndItsEntities where entityIds.contains(entity.entityId) {
|
||||
if let area = Current.areasProvider().area(for: areaId, serverId: serverId) {
|
||||
// Fetch area from database based on entity
|
||||
do {
|
||||
let areas = try AppArea.fetchAreas(containingEntity: entity.entityId, serverId: serverId)
|
||||
if let area = areas.first {
|
||||
return area.name
|
||||
}
|
||||
} catch {
|
||||
Current.Log.error("Failed to fetch area for entity from database: \(error.localizedDescription)")
|
||||
}
|
||||
return entity.entityId
|
||||
}
|
||||
@@ -89,14 +84,4 @@ final class MagicItemAddViewModel: ObservableObject {
|
||||
.filter({ $0.Scene == nil })
|
||||
.sorted(by: { $0.Position < $1.Position })
|
||||
}
|
||||
|
||||
private func loadEntitiesForAreas() async {
|
||||
for server in Current.servers.all {
|
||||
let areasAndItsEntities = await Current.areasProvider().fetchAreasAndItsEntities(for: server)
|
||||
let serverId = server.identifier.rawValue
|
||||
await MainActor.run { [serverId, areasAndItsEntities] in
|
||||
self.serversAreasAndItsEntities[serverId] = areasAndItsEntities
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,20 @@ class SettingsViewController: HAFormViewController {
|
||||
}
|
||||
|
||||
let contentSections: ContentSection
|
||||
|
||||
private var updateDatabaseTask: Task<Void, Never>?
|
||||
|
||||
deinit {
|
||||
updateDatabaseTask?.cancel()
|
||||
updateDatabaseTask = nil
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
updateDatabaseTask?.cancel()
|
||||
updateDatabaseTask = nil
|
||||
}
|
||||
|
||||
init(contentSections: ContentSection = .all) {
|
||||
self.contentSections = contentSections
|
||||
super.init()
|
||||
@@ -175,7 +189,10 @@ class SettingsViewController: HAFormViewController {
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
Current.appDatabaseUpdater.update()
|
||||
updateDatabaseTask?.cancel()
|
||||
updateDatabaseTask = Task {
|
||||
await Current.appDatabaseUpdater.update()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func openAbout(_ sender: UIButton) {
|
||||
|
||||
@@ -32,9 +32,17 @@ final class CarPlayAreasViewModel {
|
||||
|
||||
currentTask?.cancel()
|
||||
currentTask = Task {
|
||||
let areasAndEntities = await Current.areasProvider().fetchAreasAndItsEntities(for: server)
|
||||
// Fetch areas from database instead of always fetching from API
|
||||
let areas: [AppArea]
|
||||
do {
|
||||
areas = try AppArea.fetchAreas(for: server.identifier.rawValue)
|
||||
} catch {
|
||||
Current.Log.error("Failed to fetch areas from database: \(error.localizedDescription)")
|
||||
areas = []
|
||||
}
|
||||
|
||||
await MainActor.run {
|
||||
self.updateAreas(allEntitiesPerArea: areasAndEntities, server: server)
|
||||
self.updateAreas(areas: areas, server: server)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,34 +52,36 @@ final class CarPlayAreasViewModel {
|
||||
entitiesListTemplate?.entitiesStateChange(serverId: serverId, entities: entities)
|
||||
}
|
||||
|
||||
private func updateAreas(allEntitiesPerArea: [String: Set<String>], server: Server) {
|
||||
let areas = Current.areasProvider().areas[server.identifier.rawValue]
|
||||
let items = areas?.sorted(by: { a1, a2 in
|
||||
@MainActor
|
||||
private func updateAreas(areas: [AppArea], server: Server) {
|
||||
let items = areas.sorted(by: { a1, a2 in
|
||||
a1.name < a2.name
|
||||
}).compactMap { area -> CPListItem? in
|
||||
guard let entityIdsForAreaId = allEntitiesPerArea[area.areaId] else { return nil }
|
||||
// Skip areas with no entities
|
||||
guard !area.entities.isEmpty else { return nil }
|
||||
|
||||
let icon = MaterialDesignIcons(
|
||||
serversideValueNamed: area.icon ?? "mdi:circle"
|
||||
).carPlayIcon()
|
||||
let item = CPListItem(text: area.name, detailText: nil, image: icon)
|
||||
item.accessoryType = .disclosureIndicator
|
||||
item.handler = { [weak self] _, completion in
|
||||
self?.listItemHandler(area: area, entityIdsForAreaId: Array(entityIdsForAreaId), server: server)
|
||||
self?.listItemHandler(area: area, server: server)
|
||||
completion()
|
||||
}
|
||||
return item
|
||||
} ?? []
|
||||
}
|
||||
|
||||
templateProvider?.paginatedList.updateItems(items: items)
|
||||
}
|
||||
|
||||
// swiftlint:enable cyclomatic_complexity
|
||||
|
||||
private func listItemHandler(area: HAAreaResponse, entityIdsForAreaId: [String], server: Server) {
|
||||
private func listItemHandler(area: AppArea, server: Server) {
|
||||
guard let entities else { return }
|
||||
entitiesListTemplate = CarPlayEntitiesListTemplate.build(
|
||||
title: area.name,
|
||||
filterType: .areaId(entityIds: entityIdsForAreaId),
|
||||
filterType: .areaId(entityIds: Array(area.entities)),
|
||||
server: server,
|
||||
entitiesCachedStates: entities
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import UIKit
|
||||
|
||||
public protocol AppDatabaseUpdaterProtocol {
|
||||
func stop()
|
||||
func update()
|
||||
func update() async
|
||||
}
|
||||
|
||||
final class AppDatabaseUpdater: AppDatabaseUpdaterProtocol {
|
||||
@@ -27,7 +27,7 @@ final class AppDatabaseUpdater: AppDatabaseUpdaterProtocol {
|
||||
cancelOnGoingRequests()
|
||||
}
|
||||
|
||||
func update() {
|
||||
func update() async {
|
||||
cancelOnGoingRequests()
|
||||
|
||||
if let lastUpdate, lastUpdate.timeIntervalSinceNow > -5 {
|
||||
@@ -38,8 +38,9 @@ final class AppDatabaseUpdater: AppDatabaseUpdaterProtocol {
|
||||
}
|
||||
|
||||
Current.Log.verbose("Updating database, servers count \(Current.servers.all.count)")
|
||||
Current.servers.all.forEach { server in
|
||||
guard server.info.connection.activeURL() != nil else { return }
|
||||
|
||||
for server in Current.servers.all {
|
||||
guard server.info.connection.activeURL() != nil else { continue }
|
||||
// Cache entities
|
||||
let entitiesDatabaseToken = updateEntitiesDatabase(server: server)
|
||||
requestTokens.append(entitiesDatabaseToken)
|
||||
@@ -49,7 +50,7 @@ final class AppDatabaseUpdater: AppDatabaseUpdaterProtocol {
|
||||
requestTokens.append(entitiesRegistryToken)
|
||||
|
||||
// Cache areas with their entities
|
||||
updateAreasDatabase(server: server)
|
||||
await updateAreasDatabase(server: server)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,23 +96,19 @@ final class AppDatabaseUpdater: AppDatabaseUpdaterProtocol {
|
||||
)
|
||||
}
|
||||
|
||||
private func updateAreasDatabase(server: Server) {
|
||||
Task { [weak self] in
|
||||
guard let self else { return }
|
||||
private func updateAreasDatabase(server: Server) async {
|
||||
let areasAndEntities = await Current.areasProvider().fetchAreasAndItsEntities(for: server)
|
||||
|
||||
let areasAndEntities = await Current.areasProvider().fetchAreasAndItsEntities(for: server)
|
||||
|
||||
guard let areas = Current.areasProvider().areas[server.identifier.rawValue] else {
|
||||
Current.Log.verbose("No areas found for server \(server.info.name)")
|
||||
return
|
||||
}
|
||||
|
||||
await saveAreasToDatabase(
|
||||
areas: areas,
|
||||
areasAndEntities: areasAndEntities,
|
||||
serverId: server.identifier.rawValue
|
||||
)
|
||||
guard let areas = Current.areasProvider().areas[server.identifier.rawValue] else {
|
||||
Current.Log.verbose("No areas found for server \(server.info.name)")
|
||||
return
|
||||
}
|
||||
|
||||
await saveAreasToDatabase(
|
||||
areas: areas,
|
||||
areasAndEntities: areasAndEntities,
|
||||
serverId: server.identifier.rawValue
|
||||
)
|
||||
}
|
||||
|
||||
private func saveAreasToDatabase(
|
||||
|
||||
Reference in New Issue
Block a user