Skip to content

Commit

Permalink
Fixes for CarPlay lag (#3156)
Browse files Browse the repository at this point in the history
  • Loading branch information
bgoncal authored Nov 13, 2024
1 parent 4f9cdc2 commit b0988f0
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 83 deletions.
101 changes: 45 additions & 56 deletions Sources/App/Scenes/CarPlaySceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,20 @@ public protocol EntitiesStateSubscription {
@available(iOS 16.0, *)
class CarPlaySceneDelegate: UIResponder {
private var interfaceController: CPInterfaceController?
private var entities: HACache<HACachedStates>?
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?
Expand All @@ -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
Expand All @@ -72,28 +77,36 @@ 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() {
allTemplates.forEach { $0.update() }
}

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() {
Expand Down Expand Up @@ -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()
}
}

Expand All @@ -158,8 +151,4 @@ extension CarPlaySceneDelegate: CPInterfaceControllerDelegate {
func templateWillAppear(_ aTemplate: CPTemplate, animated: Bool) {
allTemplates.forEach { $0.templateWillAppear(template: aTemplate) }
}

func sceneWillEnterForeground(_ scene: UIScene) {
observeCarPlayConfigChanges()
}
}
74 changes: 47 additions & 27 deletions Sources/CarPlay/Templates/Servers/CarPlayServerListTemplate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand All @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}

Expand Down

0 comments on commit b0988f0

Please sign in to comment.