diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..521e354 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "chat.tools.terminal.autoApprove": { + "flutter": true + } +} \ No newline at end of file diff --git a/ios/Runner/CarPlaySceneDelegate.swift b/ios/Runner/CarPlaySceneDelegate.swift index a75dcac..5d59e50 100644 --- a/ios/Runner/CarPlaySceneDelegate.swift +++ b/ios/Runner/CarPlaySceneDelegate.swift @@ -14,10 +14,11 @@ import UIKit * Note: the com.apple.developer.carplay-audio entitlement must be enabled in the * Apple Developer Portal and added to the target in Xcode Signing & Capabilities. */ -class CarPlaySceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate { +class CarPlaySceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate, CPListTemplateDelegate { private var carPlayInterfaceController: CPInterfaceController? private var listTemplate: CPListTemplate? + private var streamItem: CPListItem? // MARK: - CPTemplateApplicationSceneDelegate @@ -26,7 +27,13 @@ class CarPlaySceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate { didConnect interfaceController: CPInterfaceController ) { carPlayInterfaceController = interfaceController - interfaceController.setRootTemplate(makeListTemplate(), animated: false, completion: nil) + let rootTemplate = makeListTemplate() + if #available(iOS 14.0, *) { + interfaceController.setRootTemplate(rootTemplate, animated: false, completion: nil) + } else { + // iOS 13 fallback: use API variant available before iOS 14. + interfaceController.setRootTemplate(rootTemplate, animated: false) + } } func templateApplicationScene( @@ -41,7 +48,6 @@ class CarPlaySceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate { // MARK: - Template construction private func makeListTemplate() -> CPListTemplate { - let artwork = CPListItem.maximumImageSize let icon = UIImage(named: "AppIcon")? .withRenderingMode(.alwaysOriginal) @@ -50,19 +56,29 @@ class CarPlaySceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate { detailText: "KRYZ Radio", image: icon ) - item.handler = { [weak self] _, completion in - self?.handleStreamItemTapped() - completion() + streamItem = item + + if #available(iOS 14.0, *) { + item.handler = { [weak self] _, completion in + self?.handleStreamItemTapped() + completion() + } } let section = CPListSection(items: [item]) let template = CPListTemplate(title: "KRYZ Go!", sections: [section]) - template.tabTitle = "KRYZ Go!" - template.tabImage = icon + + if #unavailable(iOS 14.0) { + // iOS 13 fallback: use list delegate selection callback. + template.delegate = self + } + + if #available(iOS 14.0, *) { + template.tabTitle = "KRYZ Go!" + template.tabImage = icon + } listTemplate = template - // Silence unused-variable warning - _ = artwork return template } @@ -70,11 +86,20 @@ class CarPlaySceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate { private func handleStreamItemTapped() { appDelegate?.invokePlay() - carPlayInterfaceController?.pushTemplate( - CPNowPlayingTemplate.shared, - animated: true, - completion: nil - ) + guard let interfaceController = carPlayInterfaceController else { + return + } + + if #available(iOS 14.0, *) { + interfaceController.pushTemplate( + CPNowPlayingTemplate.shared, + animated: true, + completion: nil + ) + } else { + // iOS 13 fallback: no CPNowPlayingTemplate.shared API. + // Keep playback running and remain on the list template. + } } // MARK: - Helpers @@ -82,4 +107,20 @@ class CarPlaySceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate { private var appDelegate: AppDelegate? { UIApplication.shared.delegate as? AppDelegate } + + // MARK: - CPListTemplateDelegate (iOS 13 fallback) + + func listTemplate( + _ listTemplate: CPListTemplate, + didSelect item: CPListItem, + completionHandler: @escaping () -> Void + ) { + defer { completionHandler() } + + guard item == streamItem else { + return + } + + handleStreamItemTapped() + } }