-
Notifications
You must be signed in to change notification settings - Fork 327
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
<!-- 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 --> CarPlay implementation that allows you to interact with various entities safely while driving your vehicle. ## 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#923 ## 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: Joshua Peisach <[email protected]> Co-authored-by: Bram Kragten <[email protected]> Co-authored-by: Bruno Pantaleão <[email protected]>
- Loading branch information
1 parent
f04c5f4
commit 14081d7
Showing
17 changed files
with
1,139 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
import CarPlay | ||
import Communicator | ||
import HAKit | ||
import PromiseKit | ||
import Shared | ||
|
||
public protocol EntitiesStateSubscription { | ||
func subscribe() | ||
func unsubscribe() | ||
} | ||
|
||
@available(iOS 16.0, *) | ||
class CarPlaySceneDelegate: UIResponder { | ||
private var interfaceController: CPInterfaceController? | ||
private var entities: HACache<Set<HAEntity>>? | ||
private var domainsListTemplate: DomainsListTemplate? | ||
private var serverId: Identifier<Server>? | ||
|
||
private let carPlayPreferredServerKey = "carPlay-server" | ||
|
||
private func setServer(server: Server) { | ||
serverId = server.identifier | ||
prefs.set(server.identifier.rawValue, forKey: carPlayPreferredServerKey) | ||
setDomainListTemplate(for: server) | ||
updateServerListButton() | ||
} | ||
|
||
private func updateServerListButton() { | ||
domainsListTemplate?.setServerListButton(show: Current.servers.all.count > 1) | ||
} | ||
|
||
@objc private func updateServerList() { | ||
DispatchQueue.main.async { [weak self] in | ||
guard let self else { return } | ||
self.updateServerListButton() | ||
if self.serverId == nil { | ||
/// No server is selected | ||
guard let server = self.getServer() else { | ||
Current.Log.info("No server connected") | ||
return | ||
} | ||
self.setServer(server: server) | ||
} | ||
} | ||
} | ||
|
||
private func showNoServerAlert() { | ||
guard interfaceController?.presentedTemplate == nil else { | ||
return | ||
} | ||
|
||
let loginAlertAction = CPAlertAction(title: L10n.Carplay.Labels.alreadyAddedServer, style: .default) { _ in | ||
if !Current.servers.all.isEmpty { | ||
self.interfaceController?.dismissTemplate(animated: true, completion: nil) | ||
} | ||
} | ||
let alertTemplate = CPAlertTemplate( | ||
titleVariants: [L10n.Carplay.Labels.noServersAvailable], | ||
actions: [loginAlertAction] | ||
) | ||
interfaceController?.presentTemplate(alertTemplate, animated: true, completion: nil) | ||
} | ||
|
||
private func setDomainListTemplate(for server: Server) { | ||
guard let interfaceController else { return } | ||
|
||
let entities = Current.api(for: server).connection.caches.states | ||
|
||
domainsListTemplate = DomainsListTemplate( | ||
title: server.info.name, | ||
entities: entities, | ||
serverButtonHandler: { [weak self] _ in | ||
self?.setServerListTemplate() | ||
}, | ||
server: server | ||
) | ||
|
||
guard let domainsListTemplate else { return } | ||
|
||
domainsListTemplate.interfaceController = interfaceController | ||
|
||
interfaceController.setRootTemplate(domainsListTemplate.template, animated: true, completion: nil) | ||
domainsListTemplate.updateSections() | ||
} | ||
|
||
private func setServerListTemplate() { | ||
var serverList: [CPListItem] = [] | ||
for server in Current.servers.all { | ||
let serverItem = CPListItem( | ||
text: server.info.name, | ||
detailText: "\(server.info.connection.activeURLType.description) - \(server.info.connection.activeURL().absoluteString)" | ||
) | ||
serverItem.handler = { [weak self] _, completion in | ||
self?.setServer(server: server) | ||
if let templates = self?.interfaceController?.templates, templates.count > 1 { | ||
self?.interfaceController?.popTemplate(animated: true, completion: nil) | ||
} | ||
completion() | ||
} | ||
serverItem.accessoryType = serverId == server.identifier ? .cloud : .none | ||
serverList.append(serverItem) | ||
} | ||
let section = CPListSection(items: serverList) | ||
let serverListTemplate = CPListTemplate(title: L10n.Carplay.Labels.servers, sections: [section]) | ||
interfaceController?.pushTemplate(serverListTemplate, animated: true, completion: nil) | ||
} | ||
|
||
private func setEmptyTemplate(interfaceController: CPInterfaceController) { | ||
interfaceController.setRootTemplate(CPInformationTemplate( | ||
title: L10n.About.Logo.title, | ||
layout: .leading, | ||
items: [], | ||
actions: [] | ||
), animated: true, completion: nil) | ||
} | ||
|
||
/// Get server for ID or first server available | ||
private func getServer(id: Identifier<Server>? = nil) -> Server? { | ||
guard let id = id else { | ||
return Current.servers.all.first | ||
} | ||
return Current.servers.server(for: id) | ||
} | ||
} | ||
|
||
// MARK: - CPTemplateApplicationSceneDelegate | ||
|
||
@available(iOS 16.0, *) | ||
extension CarPlaySceneDelegate: CPTemplateApplicationSceneDelegate { | ||
func templateApplicationScene( | ||
_ templateApplicationScene: CPTemplateApplicationScene, | ||
didConnect interfaceController: CPInterfaceController | ||
) { | ||
self.interfaceController = interfaceController | ||
self.interfaceController?.delegate = self | ||
|
||
if let serverIdentifier = prefs.string(forKey: carPlayPreferredServerKey), | ||
let selectedServer = Current.servers.server(forServerIdentifier: serverIdentifier) { | ||
setServer(server: selectedServer) | ||
} else if let server = getServer() { | ||
setServer(server: server) | ||
} else { | ||
setEmptyTemplate(interfaceController: interfaceController) | ||
} | ||
|
||
updateServerList() | ||
|
||
NotificationCenter.default.addObserver( | ||
self, | ||
selector: #selector(updateServerList), | ||
name: HAConnectionState.didTransitionToStateNotification, | ||
object: nil | ||
) | ||
|
||
NotificationCenter.default.addObserver( | ||
self, | ||
selector: #selector(updateServerList), | ||
name: HomeAssistantAPI.didConnectNotification, | ||
object: nil | ||
) | ||
|
||
/// Observer for servers list changes | ||
Current.servers.add(observer: self) | ||
|
||
if Current.servers.all.isEmpty { | ||
showNoServerAlert() | ||
} | ||
} | ||
|
||
func templateApplicationScene( | ||
_ templateApplicationScene: CPTemplateApplicationScene, | ||
didDisconnect interfaceController: CPInterfaceController, | ||
from window: CPWindow | ||
) { | ||
NotificationCenter.default.removeObserver(self) | ||
Current.servers.remove(observer: self) | ||
} | ||
} | ||
|
||
// MARK: - ServerObserver | ||
|
||
@available(iOS 16.0, *) | ||
extension CarPlaySceneDelegate: ServerObserver { | ||
func serversDidChange(_ serverManager: ServerManager) { | ||
defer { | ||
updateServerListButton() | ||
} | ||
|
||
guard let server = getServer(id: serverId) else { | ||
serverId = nil | ||
|
||
if let server = getServer() { | ||
setServer(server: server) | ||
} else if interfaceController?.presentedTemplate != nil { | ||
interfaceController?.dismissTemplate(animated: true, completion: nil) | ||
} else { | ||
showNoServerAlert() | ||
} | ||
|
||
return | ||
} | ||
setServer(server: server) | ||
} | ||
} | ||
|
||
@available(iOS 16.0, *) | ||
extension CarPlaySceneDelegate: CPInterfaceControllerDelegate { | ||
func templateWillDisappear(_ aTemplate: CPTemplate, animated: Bool) { | ||
domainsListTemplate?.templateWillDisappear(template: aTemplate) | ||
} | ||
|
||
func templateWillAppear(_ aTemplate: CPTemplate, animated: Bool) { | ||
domainsListTemplate?.templateWillAppear(template: aTemplate) | ||
} | ||
} | ||
|
||
protocol CarPlayTemplateProvider { | ||
var template: CPTemplate { get set } | ||
func templateWillDisappear(template: CPTemplate) | ||
func templateWillAppear(template: CPTemplate) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.