@iternio/react-native-auto-play 0.5.14-beta.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +214 -29
- package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/AndroidAutoSession.kt +5 -0
- package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/HybridMapTemplate.kt +9 -2
- package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/NativeBackdrop.kt +40 -0
- package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/VirtualRenderer.kt +52 -0
- package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/template/GridTemplate.kt +47 -13
- package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/template/Parser.kt +21 -2
- package/ios/Types.swift +3 -0
- package/ios/extensions/UIImage+Toggle.swift +5 -2
- package/ios/hybrid/HybridAutoPlay.swift +28 -16
- package/ios/hybrid/HybridMapTemplate.swift +22 -5
- package/ios/scenes/AutoPlayInterfaceController.swift +261 -72
- package/ios/templates/AutoPlayMapPanelDelegate.swift +190 -0
- package/ios/templates/AutoPlayTemplate.swift +24 -2
- package/ios/templates/GridTemplate.swift +57 -57
- package/ios/templates/InformationTemplate.swift +84 -9
- package/ios/templates/ListTemplate.swift +66 -11
- package/ios/templates/MapTemplate.swift +83 -68
- package/ios/templates/MessageTemplate.swift +72 -6
- package/ios/templates/Parser.swift +564 -69
- package/ios/templates/SearchTemplate.swift +1 -1
- package/ios/templates/TemplateStore.swift +5 -15
- package/ios/utils/SymbolFont.swift +13 -28
- package/lib/Constants.d.ts +4 -0
- package/lib/Constants.js +6 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/specs/MapTemplate.nitro.d.ts +3 -1
- package/lib/templates/GridTemplate.d.ts +23 -3
- package/lib/templates/GridTemplate.js +8 -0
- package/lib/templates/InformationTemplate.d.ts +38 -12
- package/lib/templates/ListTemplate.d.ts +16 -47
- package/lib/templates/MapTemplate.d.ts +26 -2
- package/lib/templates/MapTemplate.js +16 -2
- package/lib/templates/MessageTemplate.d.ts +52 -11
- package/lib/templates/MessageTemplate.js +1 -1
- package/lib/utils/NitroOptionsPanel.d.ts +103 -0
- package/lib/utils/NitroOptionsPanel.js +63 -0
- package/lib/utils/NitroSection.d.ts +89 -2
- package/lib/utils/NitroSection.js +49 -2
- package/nitrogen/generated/android/ReactNativeAutoPlay+autolinking.cmake +1 -0
- package/nitrogen/generated/android/c++/JChargingConnector.hpp +79 -0
- package/nitrogen/generated/android/c++/JGridImageSize.hpp +64 -0
- package/nitrogen/generated/android/c++/JGridTemplateConfig.hpp +7 -1
- package/nitrogen/generated/android/c++/JHybridGridTemplateSpec.cpp +4 -0
- package/nitrogen/generated/android/c++/JHybridInformationTemplateSpec.cpp +12 -0
- package/nitrogen/generated/android/c++/JHybridListTemplateSpec.cpp +12 -0
- package/nitrogen/generated/android/c++/JHybridMapTemplateSpec.cpp +78 -3
- package/nitrogen/generated/android/c++/JHybridMapTemplateSpec.hpp +2 -1
- package/nitrogen/generated/android/c++/JHybridSearchTemplateSpec.cpp +12 -0
- package/nitrogen/generated/android/c++/JInformationTemplateConfig.hpp +6 -0
- package/nitrogen/generated/android/c++/JListImageType.hpp +67 -0
- package/nitrogen/generated/android/c++/JListTemplateConfig.hpp +6 -0
- package/nitrogen/generated/android/c++/JNitroChargerLocation.hpp +115 -0
- package/nitrogen/generated/android/c++/JNitroChargerOutlet.hpp +82 -0
- package/nitrogen/generated/android/c++/JNitroOptionsPanelChargerSection.hpp +112 -0
- package/nitrogen/generated/android/c++/JNitroOptionsPanelConfig.hpp +157 -0
- package/nitrogen/generated/android/c++/JNitroOptionsPanelGridSection.hpp +102 -0
- package/nitrogen/generated/android/c++/JNitroOptionsPanelSection.cpp +30 -0
- package/nitrogen/generated/android/c++/JNitroOptionsPanelSection.hpp +129 -0
- package/nitrogen/generated/android/c++/JNitroRow.hpp +33 -3
- package/nitrogen/generated/android/c++/JNitroSection.hpp +6 -0
- package/nitrogen/generated/android/c++/JSearchTemplateConfig.hpp +6 -0
- package/nitrogen/generated/android/c++/JWaypointCoordinate.hpp +65 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/ChargingConnector.kt +30 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/GridImageSize.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/GridTemplateConfig.kt +9 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/HybridMapTemplateSpec.kt +5 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/ListImageType.kt +26 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroChargerLocation.kt +90 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroChargerOutlet.kt +70 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroOptionsPanelChargerSection.kt +61 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroOptionsPanelConfig.kt +61 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroOptionsPanelGridSection.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroOptionsPanelSection.kt +75 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroRow.kt +36 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/WaypointCoordinate.kt +61 -0
- package/nitrogen/generated/ios/ReactNativeAutoPlay-Swift-Cxx-Bridge.hpp +174 -0
- package/nitrogen/generated/ios/ReactNativeAutoPlay-Swift-Cxx-Umbrella.hpp +27 -0
- package/nitrogen/generated/ios/c++/HybridGridTemplateSpecSwift.hpp +3 -0
- package/nitrogen/generated/ios/c++/HybridInformationTemplateSpecSwift.hpp +9 -0
- package/nitrogen/generated/ios/c++/HybridListTemplateSpecSwift.hpp +9 -0
- package/nitrogen/generated/ios/c++/HybridMapTemplateSpecSwift.hpp +47 -1
- package/nitrogen/generated/ios/c++/HybridSearchTemplateSpecSwift.hpp +9 -0
- package/nitrogen/generated/ios/swift/ChargingConnector.swift +68 -0
- package/nitrogen/generated/ios/swift/GridImageSize.swift +48 -0
- package/nitrogen/generated/ios/swift/GridTemplateConfig.swift +12 -1
- package/nitrogen/generated/ios/swift/HybridMapTemplateSpec.swift +2 -1
- package/nitrogen/generated/ios/swift/HybridMapTemplateSpec_cxx.swift +31 -4
- package/nitrogen/generated/ios/swift/ListImageType.swift +52 -0
- package/nitrogen/generated/ios/swift/NitroChargerLocation.swift +161 -0
- package/nitrogen/generated/ios/swift/NitroChargerOutlet.swift +65 -0
- package/nitrogen/generated/ios/swift/NitroOptionsPanelChargerSection.swift +64 -0
- package/nitrogen/generated/ios/swift/NitroOptionsPanelConfig.swift +94 -0
- package/nitrogen/generated/ios/swift/NitroOptionsPanelGridSection.swift +53 -0
- package/nitrogen/generated/ios/swift/NitroOptionsPanelSection.swift +32 -0
- package/nitrogen/generated/ios/swift/NitroRow.swift +81 -1
- package/nitrogen/generated/ios/swift/WaypointCoordinate.swift +52 -0
- package/nitrogen/generated/shared/c++/ChargingConnector.hpp +104 -0
- package/nitrogen/generated/shared/c++/GridImageSize.hpp +84 -0
- package/nitrogen/generated/shared/c++/GridTemplateConfig.hpp +8 -1
- package/nitrogen/generated/shared/c++/HybridMapTemplateSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridMapTemplateSpec.hpp +5 -1
- package/nitrogen/generated/shared/c++/ListImageType.hpp +88 -0
- package/nitrogen/generated/shared/c++/NitroChargerLocation.hpp +131 -0
- package/nitrogen/generated/shared/c++/NitroChargerOutlet.hpp +98 -0
- package/nitrogen/generated/shared/c++/NitroOptionsPanelChargerSection.hpp +98 -0
- package/nitrogen/generated/shared/c++/NitroOptionsPanelConfig.hpp +107 -0
- package/nitrogen/generated/shared/c++/NitroOptionsPanelGridSection.hpp +91 -0
- package/nitrogen/generated/shared/c++/NitroRow.hpp +39 -2
- package/nitrogen/generated/shared/c++/WaypointCoordinate.hpp +91 -0
- package/package.json +1 -1
- package/src/Constants.ts +9 -0
- package/src/index.ts +1 -0
- package/src/specs/MapTemplate.nitro.ts +3 -1
- package/src/templates/GridTemplate.ts +38 -3
- package/src/templates/InformationTemplate.ts +60 -29
- package/src/templates/ListTemplate.ts +27 -55
- package/src/templates/MapTemplate.ts +60 -2
- package/src/templates/MessageTemplate.ts +71 -24
- package/src/utils/NitroOptionsPanel.ts +218 -0
- package/src/utils/NitroSection.ts +160 -5
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
//
|
|
2
|
+
// AutoPlayMapPanelDelegate.swift
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
|
|
6
|
+
import CarPlay
|
|
7
|
+
|
|
8
|
+
/// Shared `CPMapPanel.Delegate` for any `AutoPlayTemplate` that renders as a map panel
|
|
9
|
+
/// (`ListTemplate`, and future panel-backed templates like a grid). `CPMapPanel.delegate` is
|
|
10
|
+
/// `weak`, so the owning template must keep a strong reference to this instance for as long as
|
|
11
|
+
/// the panel exists.
|
|
12
|
+
@available(iOS 27.0, *)
|
|
13
|
+
class AutoPlayMapPanelDelegate: NSObject, CPMapPanel.Delegate {
|
|
14
|
+
private weak var template: AutoPlayTemplate?
|
|
15
|
+
private let templateId: String
|
|
16
|
+
// As of iOS 27 beta 4, CarPlay invokes panelDidShow twice for a single push. Guard against
|
|
17
|
+
// firing the JS appear callbacks more than once per panel.
|
|
18
|
+
private var hasFiredPanelDidShow = false
|
|
19
|
+
|
|
20
|
+
init(template: AutoPlayTemplate, templateId: String) {
|
|
21
|
+
self.template = template
|
|
22
|
+
self.templateId = templateId
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// There's no "will appear" delegate method, so `onWillAppear` and `onDidAppear` fire
|
|
26
|
+
/// together here. Also fires `onWillDisappear` and `onDidDisappear` on whichever panel was on top
|
|
27
|
+
/// before this one, since CPMapPanelDelegate never notifies a covered panel itself —
|
|
28
|
+
/// `panelDidShow` only tells us about the panel that just appeared.
|
|
29
|
+
func panelDidShow(_ panel: CPMapPanel) {
|
|
30
|
+
if hasFiredPanelDidShow {
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
hasFiredPanelDidShow = true
|
|
34
|
+
|
|
35
|
+
template?.onWillAppear(animated: true)
|
|
36
|
+
template?.onDidAppear(animated: true)
|
|
37
|
+
|
|
38
|
+
let templateId = self.templateId
|
|
39
|
+
|
|
40
|
+
Task { @MainActor in
|
|
41
|
+
var previousPanelId: String?
|
|
42
|
+
var mapTemplate: CPMapTemplate?
|
|
43
|
+
|
|
44
|
+
try? await RootModule.withInterfaceController { interfaceController in
|
|
45
|
+
mapTemplate = interfaceController.rootTemplate as? CPMapTemplate
|
|
46
|
+
previousPanelId =
|
|
47
|
+
interfaceController.panelTemplateIds
|
|
48
|
+
.filter { $0 != templateId }
|
|
49
|
+
.last
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// This panel owns the map template's bar buttons and map buttons while it's on top.
|
|
53
|
+
if let mapTemplate {
|
|
54
|
+
// Auto-hiding the bar shifts the panel and interrupts taps on it.
|
|
55
|
+
mapTemplate.automaticallyHidesNavigationBar = false
|
|
56
|
+
mapTemplate.hidesButtonsWithNavigationBar = false
|
|
57
|
+
|
|
58
|
+
applyPanelHeaderActions(self.template?.getPanelHeaderActions(), to: mapTemplate)
|
|
59
|
+
applyPanelMapButtons(self.template?.getPanelMapButtons(), to: mapTemplate)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
guard let previousPanelId else { return }
|
|
63
|
+
|
|
64
|
+
try? RootModule.withAutoPlayTemplate(templateId: previousPanelId) {
|
|
65
|
+
(template: AutoPlayTemplate) in
|
|
66
|
+
// let the now covered panel know it disappeared
|
|
67
|
+
template.onWillDisappear(animated: true)
|
|
68
|
+
template.onDidDisappear(animated: true)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// There's no "will disappear" delegate method, so `onWillDisappear` and `onDidDisappear`
|
|
74
|
+
/// fire together here. Also fires `onWillAppear` and `onDidAppear` on whichever panel is now on
|
|
75
|
+
/// top after this one is removed, since CPMapPanelDelegate never notifies a revealed panel
|
|
76
|
+
/// itself — `panelDidHide` only tells us about the panel that just disappeared.
|
|
77
|
+
/// https://developer.apple.com/documentation/ios-ipados-release-notes/ios-ipados-27-release-notes#CarPlay
|
|
78
|
+
func panelDidHide(_ panel: CPMapPanel) {
|
|
79
|
+
let templateId = self.templateId
|
|
80
|
+
|
|
81
|
+
Task { @MainActor in
|
|
82
|
+
var stillTracked = false
|
|
83
|
+
|
|
84
|
+
try? await RootModule.withInterfaceController { interfaceController in
|
|
85
|
+
stillTracked = interfaceController.panelTemplateIds.contains(templateId)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Already torn down elsewhere, e.g. by popToRootTemplate for a covered panel.
|
|
89
|
+
guard stillTracked else { return }
|
|
90
|
+
|
|
91
|
+
await handlePanelPopped(templateId: templateId, animated: true)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Tears down `templateId`'s panel and reveals whatever's now on top (or lets the map template
|
|
97
|
+
/// reclaim its own bar/map buttons if nothing remains). Shared by `panelDidHide` — which only
|
|
98
|
+
/// fires for a `hidePanel()`-triggered close — and `popTopEntry`'s `.panel` case, since
|
|
99
|
+
/// `popPanel()` never triggers `panelDidHide` and so has to run this itself.
|
|
100
|
+
@available(iOS 27.0, *)
|
|
101
|
+
@MainActor
|
|
102
|
+
func handlePanelPopped(templateId: String, animated: Bool) async {
|
|
103
|
+
var revealedPanelId: String?
|
|
104
|
+
var mapTemplate: CPMapTemplate?
|
|
105
|
+
|
|
106
|
+
try? await RootModule.withInterfaceController { interfaceController in
|
|
107
|
+
interfaceController.removeNavigationEntryIfPresent(templateId: templateId)
|
|
108
|
+
revealedPanelId = interfaceController.panelTemplateIds.last
|
|
109
|
+
mapTemplate = interfaceController.rootTemplate as? CPMapTemplate
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
try? RootModule.withAutoPlayTemplate(templateId: templateId) {
|
|
113
|
+
(template: AutoPlayTemplate) in
|
|
114
|
+
template.onWillDisappear(animated: animated)
|
|
115
|
+
template.onDidDisappear(animated: animated)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try? RootModule.withTemplateStore { templateStore in
|
|
119
|
+
templateStore.removeTemplate(templateId: templateId)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
HybridAutoPlay.removeListeners(templateId: templateId)
|
|
123
|
+
|
|
124
|
+
if let revealedPanelId {
|
|
125
|
+
// Another panel is now on top: it reclaims the bar/map buttons and its own appear pair, same as a fresh push.
|
|
126
|
+
try? RootModule.withAutoPlayTemplate(templateId: revealedPanelId) {
|
|
127
|
+
(revealed: AutoPlayTemplate) in
|
|
128
|
+
if let mapTemplate {
|
|
129
|
+
applyPanelHeaderActions(revealed.getPanelHeaderActions(), to: mapTemplate)
|
|
130
|
+
applyPanelMapButtons(revealed.getPanelMapButtons(), to: mapTemplate)
|
|
131
|
+
}
|
|
132
|
+
// let the now uncovered panel know it appeared again
|
|
133
|
+
revealed.onWillAppear(animated: animated)
|
|
134
|
+
revealed.onDidAppear(animated: animated)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else if let mapTemplate {
|
|
138
|
+
// No panel remains: let the map template reclaim its own bar/map buttons.
|
|
139
|
+
try? RootModule.withAutoPlayTemplate(templateId: mapTemplate.id) {
|
|
140
|
+
(root: AutoPlayTemplate) in
|
|
141
|
+
root.invalidate()
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/// Re-applies `templateId`'s header/map buttons to the root map template's nav bar, but only if
|
|
147
|
+
/// it's currently the topmost (visible) panel. `_invalidate()` (e.g. from `setHeaderActions()`)
|
|
148
|
+
/// runs for covered panels too, and only the topmost panel owns the nav bar — applying
|
|
149
|
+
/// unconditionally would clobber whichever panel is actually on top. A covered panel's own
|
|
150
|
+
/// buttons get reapplied later, when it's revealed again (see `handlePanelPopped`).
|
|
151
|
+
@available(iOS 27.0, *)
|
|
152
|
+
@MainActor
|
|
153
|
+
func reapplyPanelBarIfVisible(_ template: AutoPlayTemplate, templateId: String) {
|
|
154
|
+
guard
|
|
155
|
+
let scene = SceneStore.getRootScene(),
|
|
156
|
+
let interfaceController = scene.interfaceController,
|
|
157
|
+
interfaceController.panelTemplateIds.last == templateId,
|
|
158
|
+
let mapTemplate = interfaceController.rootTemplate as? CPMapTemplate
|
|
159
|
+
else { return }
|
|
160
|
+
|
|
161
|
+
applyPanelHeaderActions(template.getPanelHeaderActions(), to: mapTemplate)
|
|
162
|
+
applyPanelMapButtons(template.getPanelMapButtons(), to: mapTemplate)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/// apply header buttons according to mapConfig.headerActions or remove the map provided buttons in case none are specified for the panel
|
|
166
|
+
@available(iOS 27.0, *)
|
|
167
|
+
@MainActor
|
|
168
|
+
private func applyPanelHeaderActions(_ headerActions: [NitroAction]?, to mapTemplate: CPMapTemplate) {
|
|
169
|
+
guard let headerActions else {
|
|
170
|
+
mapTemplate.backButton = nil
|
|
171
|
+
mapTemplate.leadingNavigationBarButtons = []
|
|
172
|
+
mapTemplate.trailingNavigationBarButtons = []
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
setBarButtons(template: mapTemplate, barButtons: headerActions)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/// Applies mapConfig.mapButtons to the map template, or clears them if the panel specifies none.
|
|
180
|
+
@available(iOS 27.0, *)
|
|
181
|
+
@MainActor
|
|
182
|
+
private func applyPanelMapButtons(_ mapButtons: [NitroMapButton]?, to mapTemplate: CPMapTemplate) {
|
|
183
|
+
// Only need the root MapTemplate for its onPanButtonPress callback, since a .pan button still controls the same underlying map.
|
|
184
|
+
try? RootModule.withAutoPlayTemplate(templateId: mapTemplate.id) { (root: MapTemplate) in
|
|
185
|
+
mapTemplate.mapButtons = Parser.parseMapButtons(
|
|
186
|
+
mapButtons: mapButtons ?? [],
|
|
187
|
+
onPanButtonPress: root.onPanButtonPress
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -10,10 +10,30 @@ import CarPlay
|
|
|
10
10
|
class AutoPlayTemplate: NSObject {
|
|
11
11
|
public private(set) var autoDismissMs: Double?
|
|
12
12
|
|
|
13
|
-
func getTemplate() -> CPTemplate {
|
|
13
|
+
func getTemplate() throws -> CPTemplate {
|
|
14
14
|
fatalError("getTemplate not implemented")
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/// Templates that render as an overlay panel on an existing root `CPMapTemplate`
|
|
18
|
+
/// (iOS 27+, e.g. `ListTemplate` with `mapConfig` set) override this to return their
|
|
19
|
+
/// `CPMapPanel` instead of providing a `CPTemplate` via `getTemplate()`. Returned as `Any`
|
|
20
|
+
/// since `CPMapPanel` is only available on iOS 27+ while this class supports iOS 15.1+.
|
|
21
|
+
func getPanel() -> Any? {
|
|
22
|
+
return nil
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// Header actions to apply to the root map template's bar while this template renders as
|
|
26
|
+
/// a map panel (see `getPanel()`) — distinct from any `barButtons` used for a plain
|
|
27
|
+
/// `CPTemplate`, since the panel itself has no nav bar of its own.
|
|
28
|
+
func getPanelHeaderActions() -> [NitroAction]? {
|
|
29
|
+
return nil
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Map buttons to apply to the root map template while this renders as a map panel (see `getPanel()`).
|
|
33
|
+
func getPanelMapButtons() -> [NitroMapButton]? {
|
|
34
|
+
return nil
|
|
35
|
+
}
|
|
36
|
+
|
|
17
37
|
@MainActor final func invalidate() {
|
|
18
38
|
if SceneStore.getRootScene() == nil {
|
|
19
39
|
return
|
|
@@ -25,7 +45,9 @@ class AutoPlayTemplate: NSObject {
|
|
|
25
45
|
/// Override in subclasses to perform template invalidation.
|
|
26
46
|
/// Do not call this method directly. Call `invalidate()` instead.
|
|
27
47
|
@MainActor func _invalidate() {}
|
|
28
|
-
@MainActor func traitCollectionDidChange() {
|
|
48
|
+
@MainActor func traitCollectionDidChange() {
|
|
49
|
+
invalidate()
|
|
50
|
+
}
|
|
29
51
|
|
|
30
52
|
func onWillAppear(animated: Bool) {}
|
|
31
53
|
func onDidAppear(animated: Bool) {}
|
|
@@ -8,92 +8,92 @@
|
|
|
8
8
|
import CarPlay
|
|
9
9
|
|
|
10
10
|
class GridTemplate: AutoPlayHeaderProviding {
|
|
11
|
-
|
|
11
|
+
/// `nil` when this template renders as a map panel instead (see `mapPanel`).
|
|
12
|
+
private(set) var template: CPGridTemplate?
|
|
12
13
|
var config: GridTemplateConfig
|
|
13
14
|
|
|
14
15
|
var buttons: [NitroGridButton]
|
|
15
16
|
|
|
17
|
+
/// Boxed as `Any` since `CPMapPanel` itself is only available on iOS 27+ while this class supports a much lower deployment target.
|
|
18
|
+
private var mapPanel: Any?
|
|
19
|
+
|
|
20
|
+
/// `CPMapPanel.delegate` is `weak`, so this keeps the shared delegate alive for as long as the panel exists. Boxed as `Any` for the same reason as `mapPanel`.
|
|
21
|
+
private var mapPanelDelegate: Any?
|
|
22
|
+
|
|
16
23
|
override var autoDismissMs: Double? {
|
|
17
24
|
return config.autoDismissMs
|
|
18
25
|
}
|
|
19
26
|
|
|
20
|
-
override func getTemplate() -> CPTemplate {
|
|
27
|
+
override func getTemplate() throws -> CPTemplate {
|
|
28
|
+
guard let template else {
|
|
29
|
+
throw AutoPlayError.templateIsMapPanel(config.id)
|
|
30
|
+
}
|
|
21
31
|
return template
|
|
22
32
|
}
|
|
23
33
|
|
|
34
|
+
override func getPanel() -> Any? {
|
|
35
|
+
return mapPanel
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
override func getPanelHeaderActions() -> [NitroAction]? {
|
|
39
|
+
return barButtons
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
override func getPanelMapButtons() -> [NitroMapButton]? {
|
|
43
|
+
return config.mapConfig?.mapButtons
|
|
44
|
+
}
|
|
45
|
+
|
|
24
46
|
init(config: GridTemplateConfig) {
|
|
25
47
|
self.config = config
|
|
26
48
|
buttons = config.buttons
|
|
27
49
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
50
|
+
let title = Parser.parseText(text: config.title)
|
|
51
|
+
|
|
52
|
+
if #available(iOS 27.0, *), config.mapConfig != nil {
|
|
53
|
+
mapPanel = CPMapPanel(
|
|
54
|
+
title: title,
|
|
55
|
+
sections: [CPMapPanelSection(title: nil, items: [])],
|
|
56
|
+
buttonConfiguration: nil
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
template = CPGridTemplate(
|
|
61
|
+
title: title,
|
|
62
|
+
gridButtons: GridTemplate.parseButtons(buttons: buttons),
|
|
63
|
+
id: config.id
|
|
64
|
+
)
|
|
65
|
+
}
|
|
33
66
|
|
|
34
67
|
super.init()
|
|
35
68
|
|
|
36
69
|
barButtons = config.headerActions
|
|
37
70
|
|
|
71
|
+
if #available(iOS 27.0, *), let mapPanel = mapPanel as? CPMapPanel {
|
|
72
|
+
let delegate = AutoPlayMapPanelDelegate(template: self, templateId: config.id)
|
|
73
|
+
mapPanelDelegate = delegate
|
|
74
|
+
mapPanel.delegate = delegate
|
|
75
|
+
}
|
|
38
76
|
}
|
|
39
77
|
|
|
40
78
|
static func parseButtons(buttons: [NitroGridButton]) -> [CPGridButton] {
|
|
41
|
-
let
|
|
42
|
-
|
|
43
|
-
if #available(iOS 26.0, *) {
|
|
44
|
-
gridButtonHeight = CPGridTemplate.maximumGridButtonImageSize.height
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
gridButtonHeight = 44
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
guard let traitCollection = SceneStore.getRootTraitCollection() else {
|
|
51
|
-
return []
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return buttons.compactMap { button in
|
|
55
|
-
var image: UIImage?
|
|
56
|
-
|
|
57
|
-
if let glyphImage = button.image.glyphImage {
|
|
58
|
-
image = SymbolFont.imageFromNitroImage(
|
|
59
|
-
image: glyphImage,
|
|
60
|
-
size: gridButtonHeight,
|
|
61
|
-
traitCollection: traitCollection
|
|
62
|
-
)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if let assetImage = button.image.assetImage {
|
|
66
|
-
image = Parser.parseAssetImage(
|
|
67
|
-
assetImage: assetImage,
|
|
68
|
-
traitCollection: traitCollection
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if let remoteImage = button.image.remoteImage {
|
|
73
|
-
image = Parser.parseRemoteImage(
|
|
74
|
-
remoteImage: remoteImage,
|
|
75
|
-
traitCollection: traitCollection
|
|
76
|
-
)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
guard let image = image else { return nil }
|
|
80
|
-
guard let title = Parser.parseText(text: button.title) else { return nil }
|
|
81
|
-
|
|
82
|
-
return CPGridButton(
|
|
83
|
-
titleVariants: [title],
|
|
84
|
-
image: image
|
|
85
|
-
) { _ in
|
|
86
|
-
button.onPress()
|
|
87
|
-
}
|
|
88
|
-
}
|
|
79
|
+
guard let traitCollection = SceneStore.getRootTraitCollection() else { return [] }
|
|
80
|
+
return Parser.parseGridButtons(buttons: buttons, traitCollection: traitCollection)
|
|
89
81
|
}
|
|
90
82
|
|
|
91
83
|
@MainActor
|
|
92
84
|
override func _invalidate() {
|
|
85
|
+
if #available(iOS 27.0, *), let mapPanel = mapPanel as? CPMapPanel {
|
|
86
|
+
let parsedButtons = GridTemplate.parseButtons(buttons: buttons)
|
|
87
|
+
mapPanel.sections.first?.items = [CPMapPanelItem(gridButtons: parsedButtons)]
|
|
88
|
+
reapplyPanelBarIfVisible(self, templateId: config.id)
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
guard let template else { return }
|
|
93
|
+
|
|
93
94
|
setBarButtons(template: template, barButtons: barButtons)
|
|
94
95
|
|
|
95
|
-
|
|
96
|
-
template.updateGridButtons(buttons)
|
|
96
|
+
template.updateGridButtons(GridTemplate.parseButtons(buttons: buttons))
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
override func onWillAppear(animated: Bool) {
|
|
@@ -8,31 +8,69 @@
|
|
|
8
8
|
import CarPlay
|
|
9
9
|
|
|
10
10
|
class InformationTemplate: AutoPlayHeaderProviding {
|
|
11
|
-
|
|
11
|
+
/// `nil` when this template renders as a map panel instead (see `mapPanel`).
|
|
12
|
+
private(set) var template: CPInformationTemplate?
|
|
12
13
|
var config: InformationTemplateConfig
|
|
13
14
|
|
|
14
15
|
var section: NitroSection
|
|
15
16
|
|
|
17
|
+
/// Whether this template renders as a `CPMapPanel` instead of a plain `CPInformationTemplate`.
|
|
18
|
+
private let usesMapPanel: Bool
|
|
19
|
+
|
|
20
|
+
/// Built lazily on the first `_invalidate()` call rather than in `init()`: `CPMapPanel.buttonConfiguration`
|
|
21
|
+
/// is readonly, so unlike sections it can't be patched in later, and building it needs
|
|
22
|
+
/// `traitCollection` for the optional symbol button image — only safe to fetch on the
|
|
23
|
+
/// main thread, which `init()` isn't guaranteed to run on. Boxed as `Any` since `CPMapPanel`
|
|
24
|
+
/// itself is only available on iOS 27+ while this class supports a much lower deployment target.
|
|
25
|
+
private var mapPanel: Any?
|
|
26
|
+
|
|
27
|
+
/// `CPMapPanel.delegate` is `weak`, so this keeps the shared delegate alive for as long as the panel exists. Boxed as `Any` for the same reason as `mapPanel`.
|
|
28
|
+
private var mapPanelDelegate: Any?
|
|
29
|
+
|
|
16
30
|
override var autoDismissMs: Double? {
|
|
17
31
|
return config.autoDismissMs
|
|
18
32
|
}
|
|
19
33
|
|
|
20
|
-
override func getTemplate() -> CPTemplate {
|
|
34
|
+
override func getTemplate() throws -> CPTemplate {
|
|
35
|
+
guard let template else {
|
|
36
|
+
throw AutoPlayError.templateIsMapPanel(config.id)
|
|
37
|
+
}
|
|
21
38
|
return template
|
|
22
39
|
}
|
|
23
40
|
|
|
41
|
+
override func getPanel() -> Any? {
|
|
42
|
+
return mapPanel
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
override func getPanelHeaderActions() -> [NitroAction]? {
|
|
46
|
+
return barButtons
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
override func getPanelMapButtons() -> [NitroMapButton]? {
|
|
50
|
+
return config.mapConfig?.mapButtons
|
|
51
|
+
}
|
|
52
|
+
|
|
24
53
|
init(config: InformationTemplateConfig) {
|
|
25
54
|
self.config = config
|
|
26
55
|
|
|
27
56
|
section = config.section
|
|
28
57
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
58
|
+
if #available(iOS 27.0, *) {
|
|
59
|
+
usesMapPanel = config.mapConfig != nil
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
usesMapPanel = false
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if !usesMapPanel {
|
|
66
|
+
template = CPInformationTemplate(
|
|
67
|
+
title: Parser.parseText(text: config.title)!,
|
|
68
|
+
layout: .leading,
|
|
69
|
+
items: Parser.parseInformationItems(section: section),
|
|
70
|
+
actions: Parser.parseInformationActions(actions: config.actions),
|
|
71
|
+
id: config.id
|
|
72
|
+
)
|
|
73
|
+
}
|
|
36
74
|
|
|
37
75
|
super.init()
|
|
38
76
|
|
|
@@ -41,6 +79,43 @@ class InformationTemplate: AutoPlayHeaderProviding {
|
|
|
41
79
|
|
|
42
80
|
@MainActor
|
|
43
81
|
override func _invalidate() {
|
|
82
|
+
if usesMapPanel, #available(iOS 27.0, *) {
|
|
83
|
+
guard let mapPanel = mapPanel as? CPMapPanel else {
|
|
84
|
+
guard let traitCollection = SceneStore.getRootTraitCollection() else { return }
|
|
85
|
+
|
|
86
|
+
// InformationTemplate's actions are only ever set once and never updated
|
|
87
|
+
// afterwards, so there's nothing to keep in sync with buttonConfiguration later.
|
|
88
|
+
let panel = CPMapPanel(
|
|
89
|
+
title: Parser.parseText(text: config.title),
|
|
90
|
+
sections: [
|
|
91
|
+
CPMapPanelSection(
|
|
92
|
+
title: nil,
|
|
93
|
+
items: Parser.parseInformationPanelItems(section: section)
|
|
94
|
+
)
|
|
95
|
+
],
|
|
96
|
+
buttonConfiguration: Parser.parsePanelButtonConfiguration(
|
|
97
|
+
actions: config.actions,
|
|
98
|
+
traitCollection: traitCollection
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
mapPanel = panel
|
|
103
|
+
|
|
104
|
+
let delegate = AutoPlayMapPanelDelegate(template: self, templateId: config.id)
|
|
105
|
+
mapPanelDelegate = delegate
|
|
106
|
+
panel.delegate = delegate
|
|
107
|
+
|
|
108
|
+
reapplyPanelBarIfVisible(self, templateId: config.id)
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
mapPanel.sections.first?.items = Parser.parseInformationPanelItems(section: section)
|
|
113
|
+
reapplyPanelBarIfVisible(self, templateId: config.id)
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
guard let template else { return }
|
|
118
|
+
|
|
44
119
|
setBarButtons(template: template, barButtons: barButtons)
|
|
45
120
|
template.items = Parser.parseInformationItems(section: section)
|
|
46
121
|
}
|
|
@@ -8,44 +8,99 @@
|
|
|
8
8
|
import CarPlay
|
|
9
9
|
|
|
10
10
|
class ListTemplate: AutoPlayHeaderProviding {
|
|
11
|
-
|
|
11
|
+
/// `nil` when this template renders as a map panel instead (see `mapPanel`).
|
|
12
|
+
private(set) var template: CPListTemplate?
|
|
12
13
|
var config: ListTemplateConfig
|
|
13
14
|
|
|
14
15
|
var sections: [NitroSection]?
|
|
15
16
|
|
|
17
|
+
/// Boxed as `Any` since `CPMapPanel` itself is only available on
|
|
18
|
+
/// iOS 27+ while this class supports a much lower deployment target.
|
|
19
|
+
private var mapPanel: Any?
|
|
20
|
+
|
|
21
|
+
/// `CPMapPanel.delegate` is `weak`, so this keeps the shared delegate alive for as long as
|
|
22
|
+
/// the panel exists. Boxed as `Any` for the same reason as `mapPanel`.
|
|
23
|
+
private var mapPanelDelegate: Any?
|
|
24
|
+
|
|
16
25
|
override var autoDismissMs: Double? {
|
|
17
26
|
return config.autoDismissMs
|
|
18
27
|
}
|
|
19
28
|
|
|
20
|
-
override func getTemplate() -> CPTemplate {
|
|
29
|
+
override func getTemplate() throws -> CPTemplate {
|
|
30
|
+
guard let template else {
|
|
31
|
+
throw AutoPlayError.templateIsMapPanel(config.id)
|
|
32
|
+
}
|
|
21
33
|
return template
|
|
22
34
|
}
|
|
23
35
|
|
|
36
|
+
override func getPanel() -> Any? {
|
|
37
|
+
return mapPanel
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
override func getPanelHeaderActions() -> [NitroAction]? {
|
|
41
|
+
return barButtons
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
override func getPanelMapButtons() -> [NitroMapButton]? {
|
|
45
|
+
return config.mapConfig?.mapButtons
|
|
46
|
+
}
|
|
47
|
+
|
|
24
48
|
init(config: ListTemplateConfig) {
|
|
25
49
|
self.config = config
|
|
26
50
|
|
|
27
51
|
sections = config.sections
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
52
|
+
let title = Parser.parseText(text: config.title)
|
|
53
|
+
|
|
54
|
+
if #available(iOS 27.0, *), config.mapConfig != nil {
|
|
55
|
+
mapPanel = CPMapPanel(
|
|
56
|
+
title: title,
|
|
57
|
+
sections: (config.sections ?? []).map { section in
|
|
58
|
+
CPMapPanelSection(title: section.title, items: [])
|
|
59
|
+
},
|
|
60
|
+
buttonConfiguration: nil
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
template = CPListTemplate(
|
|
65
|
+
title: title,
|
|
66
|
+
sections: [],
|
|
67
|
+
assistantCellConfiguration: nil,
|
|
68
|
+
id: config.id
|
|
69
|
+
)
|
|
70
|
+
}
|
|
35
71
|
|
|
36
72
|
super.init()
|
|
37
73
|
|
|
38
74
|
barButtons = config.headerActions
|
|
75
|
+
|
|
76
|
+
if #available(iOS 27.0, *), let mapPanel = mapPanel as? CPMapPanel {
|
|
77
|
+
let delegate = AutoPlayMapPanelDelegate(template: self, templateId: config.id)
|
|
78
|
+
mapPanelDelegate = delegate
|
|
79
|
+
mapPanel.delegate = delegate
|
|
80
|
+
}
|
|
39
81
|
}
|
|
40
82
|
|
|
41
83
|
@MainActor
|
|
42
84
|
override func _invalidate() {
|
|
43
|
-
setBarButtons(template: template, barButtons: barButtons)
|
|
44
|
-
|
|
45
85
|
guard let traitCollection = SceneStore.getRootTraitCollection() else {
|
|
46
86
|
return
|
|
47
87
|
}
|
|
48
88
|
|
|
89
|
+
if #available(iOS 27.0, *), let mapPanel = mapPanel as? CPMapPanel {
|
|
90
|
+
mapPanel.sections = Parser.parseMapPanelSections(
|
|
91
|
+
sections: sections,
|
|
92
|
+
updateSection: self.updateSection(section:sectionIndex:),
|
|
93
|
+
traitCollection: traitCollection
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
reapplyPanelBarIfVisible(self, templateId: config.id)
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
guard let template else { return }
|
|
101
|
+
|
|
102
|
+
setBarButtons(template: template, barButtons: barButtons)
|
|
103
|
+
|
|
49
104
|
template.updateSections(
|
|
50
105
|
Parser.parseSections(
|
|
51
106
|
sections: sections,
|