From b0988f04106d791591ceeaba7dc084ef43ea8e7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pantale=C3=A3o=20Gon=C3=A7alves?= <5808343+bgoncal@users.noreply.github.com> Date: Wed, 13 Nov 2024 18:11:45 +0100 Subject: [PATCH] Fixes for CarPlay lag (#3156) --- Sources/App/Scenes/CarPlaySceneDelegate.swift | 101 ++++++++---------- .../Servers/CarPlayServerListTemplate.swift | 74 ++++++++----- .../Servers/CarPlayServerListViewModel.swift | 2 + 3 files changed, 94 insertions(+), 83 deletions(-) diff --git a/Sources/App/Scenes/CarPlaySceneDelegate.swift b/Sources/App/Scenes/CarPlaySceneDelegate.swift index 91df35ea7..683f08b50 100644 --- a/Sources/App/Scenes/CarPlaySceneDelegate.swift +++ b/Sources/App/Scenes/CarPlaySceneDelegate.swift @@ -14,16 +14,20 @@ public protocol EntitiesStateSubscription { @available(iOS 16.0, *) class CarPlaySceneDelegate: UIResponder { private var interfaceController: CPInterfaceController? - private var entities: HACache? private var entitiesSubscriptionToken: HACancellable? - private var domainsListTemplate: any CarPlayTemplateProvider - private var serversListTemplate: any CarPlayTemplateProvider - private var quickAccessListTemplate: any CarPlayTemplateProvider - private var areasZonesListTemplate: any CarPlayTemplateProvider + private var domainsListTemplate: (any CarPlayTemplateProvider)? + private var serversListTemplate: (any CarPlayTemplateProvider)? + private var quickAccessListTemplate: (any CarPlayTemplateProvider)? + private var areasZonesListTemplate: (any CarPlayTemplateProvider)? private var allTemplates: [any CarPlayTemplateProvider] { - [quickAccessListTemplate, areasZonesListTemplate, domainsListTemplate, serversListTemplate] + [ + quickAccessListTemplate, + areasZonesListTemplate, + domainsListTemplate, + serversListTemplate, + ].compactMap({ $0 }) } private var cachedConfig: CarPlayConfig? @@ -33,35 +37,36 @@ class CarPlaySceneDelegate: UIResponder { prefs.string(forKey: CarPlayServersListTemplate.carPlayPreferredServerKey) ?? "" } - override init() { - self.domainsListTemplate = CarPlayDomainsListTemplate.build() - self.serversListTemplate = CarPlayServersListTemplate.build() - self.quickAccessListTemplate = CarPlayQuickAccessTemplate.build() - self.areasZonesListTemplate = CarPlayAreasZonesTemplate.build() - super.init() + func setup() { + observeCarPlayConfigChanges() + subscribeToEntitiesChanges() } private func setTemplates(config: CarPlayConfig?) { - var visibleTemplates = allTemplates - - // In case config exists, we will only show the tabs that are enabled + var visibleTemplates: [any CarPlayTemplateProvider] = [] if let config { guard config != cachedConfig else { return } cachedConfig = config - visibleTemplates = config.tabs.map { + visibleTemplates = config.tabs.compactMap { switch $0 { case .quickAccess: - // Reload the quick access list template - quickAccessListTemplate = CarPlayQuickAccessTemplate.build() + buildQuickAccessTab() return quickAccessListTemplate case .areas: + areasZonesListTemplate = CarPlayAreasZonesTemplate.build() return areasZonesListTemplate case .domains: + domainsListTemplate = CarPlayDomainsListTemplate.build() return domainsListTemplate case .settings: + buildServerTab() return serversListTemplate } } + } else { + buildQuickAccessTab() + buildServerTab() + visibleTemplates = allTemplates } let tabBar = CPTabBarTemplate(templates: visibleTemplates.map { templateProvider in @@ -72,11 +77,21 @@ class CarPlaySceneDelegate: UIResponder { updateTemplates() } + private func buildQuickAccessTab() { + quickAccessListTemplate = CarPlayQuickAccessTemplate.build() + } + + private func buildServerTab() { + serversListTemplate = CarPlayServersListTemplate.build() + // So it can reload in case of server changes + (serversListTemplate as? CarPlayServersListTemplate)?.sceneDelegate = self + } + private func setInterfaceControllerForChildren() { - domainsListTemplate.interfaceController = interfaceController - serversListTemplate.interfaceController = interfaceController - quickAccessListTemplate.interfaceController = interfaceController - areasZonesListTemplate.interfaceController = interfaceController + domainsListTemplate?.interfaceController = interfaceController + serversListTemplate?.interfaceController = interfaceController + quickAccessListTemplate?.interfaceController = interfaceController + areasZonesListTemplate?.interfaceController = interfaceController } @objc private func updateTemplates() { @@ -84,16 +99,14 @@ class CarPlaySceneDelegate: UIResponder { } private func subscribeToEntitiesChanges() { - let server = Current.servers.server(forServerIdentifier: preferredServerId) ?? Current.servers.all.first - - guard let server, entitiesSubscriptionToken == nil else { return } - entities = Current.api(for: server).connection.caches.states + guard let server = Current.servers.server(forServerIdentifier: preferredServerId) ?? Current.servers.all.first else { return } entitiesSubscriptionToken?.cancel() - entitiesSubscriptionToken = entities?.subscribe { [weak self] _, states in - self?.allTemplates.forEach { - $0.entitiesStateChange(entities: states) + entitiesSubscriptionToken = Current.api(for: server).connection.caches.states + .subscribe { [weak self] _, states in + self?.allTemplates.forEach { + $0.entitiesStateChange(entities: states) + } } - } } private func observeCarPlayConfigChanges() { @@ -122,30 +135,10 @@ extension CarPlaySceneDelegate: CPTemplateApplicationSceneDelegate { ) { self.interfaceController = interfaceController self.interfaceController?.delegate = self - - NotificationCenter.default.addObserver( - self, - selector: #selector(updateTemplates), - name: HAConnectionState.didTransitionToStateNotification, - object: nil - ) - - NotificationCenter.default.addObserver( - self, - selector: #selector(updateTemplates), - name: HomeAssistantAPI.didConnectNotification, - object: nil - ) - - subscribeToEntitiesChanges() } - func templateApplicationScene( - _ templateApplicationScene: CPTemplateApplicationScene, - didDisconnect interfaceController: CPInterfaceController, - from window: CPWindow - ) { - NotificationCenter.default.removeObserver(self) + func sceneWillEnterForeground(_ scene: UIScene) { + setup() } } @@ -158,8 +151,4 @@ extension CarPlaySceneDelegate: CPInterfaceControllerDelegate { func templateWillAppear(_ aTemplate: CPTemplate, animated: Bool) { allTemplates.forEach { $0.templateWillAppear(template: aTemplate) } } - - func sceneWillEnterForeground(_ scene: UIScene) { - observeCarPlayConfigChanges() - } } diff --git a/Sources/CarPlay/Templates/Servers/CarPlayServerListTemplate.swift b/Sources/CarPlay/Templates/Servers/CarPlayServerListTemplate.swift index 0be401e3c..111e51a58 100644 --- a/Sources/CarPlay/Templates/Servers/CarPlayServerListTemplate.swift +++ b/Sources/CarPlay/Templates/Servers/CarPlayServerListTemplate.swift @@ -10,6 +10,7 @@ final class CarPlayServersListTemplate: CarPlayTemplateProvider { private let viewModel: CarPlayServerListViewModel var template: CPListTemplate + weak var sceneDelegate: CarPlaySceneDelegate? weak var interfaceController: CPInterfaceController? { didSet { viewModel.interfaceController = interfaceController @@ -39,37 +40,32 @@ final class CarPlayServersListTemplate: CarPlayTemplateProvider { } func entitiesStateChange(entities: HACachedStates) { - // TODO: Implement + /* no-op */ } @objc func update() { - var serverList: [CPListItem] = [] - for serverOption in Current.servers.all { - let serverItem = CPListItem( - text: serverOption.info.name, - detailText: nil - ) - serverItem.handler = { [weak self] _, completion in - self?.viewModel.setServer(server: serverOption) - completion() - } - serverItem.accessoryType = viewModel.preferredServerId == serverOption.identifier.rawValue ? .cloud : .none - serverList.append(serverItem) + let serverList: [CPListItem] = Current.servers.all.filter({ + // Only display servers that can be used in the user current environment + $0.info.connection.activeURL() != nil + }).compactMap { server in + serverItem(server: server) } - let section = CPListSection(items: serverList, header: L10n.CarPlay.Labels.selectServer, sectionIndexTitle: nil) - let advancedSection = CPListSection(items: [ - { - let item = CPListItem( - text: L10n.CarPlay.Labels.Settings.Advanced.Section.Button.title, - detailText: L10n.CarPlay.Labels.Settings.Advanced.Section.Button.detail - ) - item.handler = { _, _ in - fatalError("Intentional crash, triggered from CarPlay advanced option to restart App.") - } - return item - }(), - ], header: L10n.CarPlay.Labels.Settings.Advanced.Section.title, sectionIndexTitle: nil) - template.updateSections([section, advancedSection]) + let serversSection = CPListSection( + items: serverList, + header: L10n.CarPlay.Labels.selectServer, + sectionIndexTitle: nil + ) + + let advancedSection = CPListSection( + items: [restartItem], + header: L10n.CarPlay.Labels.Settings.Advanced.Section.title, + sectionIndexTitle: nil + ) + + template.updateSections([ + serversSection, + advancedSection, + ]) } func showNoServerAlert() { @@ -81,4 +77,28 @@ final class CarPlayServersListTemplate: CarPlayTemplateProvider { alertTemplate.interfaceController = interfaceController alertTemplate.present() } + + private func serverItem(server: Server) -> CPListItem { + let serverItem = CPListItem( + text: server.info.name, + detailText: nil + ) + serverItem.handler = { [weak self] _, completion in + self?.viewModel.setServer(server: server) + completion() + } + serverItem.accessoryType = viewModel.preferredServerId == server.identifier.rawValue ? .cloud : .none + return serverItem + } + + private var restartItem: CPListItem { + let item = CPListItem( + text: L10n.CarPlay.Labels.Settings.Advanced.Section.Button.title, + detailText: L10n.CarPlay.Labels.Settings.Advanced.Section.Button.detail + ) + item.handler = { _, _ in + fatalError("Intentional crash, triggered from CarPlay advanced option to restart App.") + } + return item + } } diff --git a/Sources/CarPlay/Templates/Servers/CarPlayServerListViewModel.swift b/Sources/CarPlay/Templates/Servers/CarPlayServerListViewModel.swift index 72ede00d9..55314bd35 100644 --- a/Sources/CarPlay/Templates/Servers/CarPlayServerListViewModel.swift +++ b/Sources/CarPlay/Templates/Servers/CarPlayServerListViewModel.swift @@ -17,12 +17,14 @@ final class CarPlayServerListViewModel { } func addServerObserver() { + removeServerObserver() Current.servers.add(observer: self) } func setServer(server: Server) { prefs.set(server.identifier.rawValue, forKey: CarPlayServersListTemplate.carPlayPreferredServerKey) templateProvider?.update() + templateProvider?.sceneDelegate?.setup() } }