@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.
Files changed (122) hide show
  1. package/README.md +214 -29
  2. package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/AndroidAutoSession.kt +5 -0
  3. package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/HybridMapTemplate.kt +9 -2
  4. package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/NativeBackdrop.kt +40 -0
  5. package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/VirtualRenderer.kt +52 -0
  6. package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/template/GridTemplate.kt +47 -13
  7. package/android/src/main/java/com/margelo/nitro/swe/iternio/reactnativeautoplay/template/Parser.kt +21 -2
  8. package/ios/Types.swift +3 -0
  9. package/ios/extensions/UIImage+Toggle.swift +5 -2
  10. package/ios/hybrid/HybridAutoPlay.swift +28 -16
  11. package/ios/hybrid/HybridMapTemplate.swift +22 -5
  12. package/ios/scenes/AutoPlayInterfaceController.swift +261 -72
  13. package/ios/templates/AutoPlayMapPanelDelegate.swift +190 -0
  14. package/ios/templates/AutoPlayTemplate.swift +24 -2
  15. package/ios/templates/GridTemplate.swift +57 -57
  16. package/ios/templates/InformationTemplate.swift +84 -9
  17. package/ios/templates/ListTemplate.swift +66 -11
  18. package/ios/templates/MapTemplate.swift +83 -68
  19. package/ios/templates/MessageTemplate.swift +72 -6
  20. package/ios/templates/Parser.swift +564 -69
  21. package/ios/templates/SearchTemplate.swift +1 -1
  22. package/ios/templates/TemplateStore.swift +5 -15
  23. package/ios/utils/SymbolFont.swift +13 -28
  24. package/lib/Constants.d.ts +4 -0
  25. package/lib/Constants.js +6 -0
  26. package/lib/index.d.ts +1 -0
  27. package/lib/index.js +1 -0
  28. package/lib/specs/MapTemplate.nitro.d.ts +3 -1
  29. package/lib/templates/GridTemplate.d.ts +23 -3
  30. package/lib/templates/GridTemplate.js +8 -0
  31. package/lib/templates/InformationTemplate.d.ts +38 -12
  32. package/lib/templates/ListTemplate.d.ts +16 -47
  33. package/lib/templates/MapTemplate.d.ts +26 -2
  34. package/lib/templates/MapTemplate.js +16 -2
  35. package/lib/templates/MessageTemplate.d.ts +52 -11
  36. package/lib/templates/MessageTemplate.js +1 -1
  37. package/lib/utils/NitroOptionsPanel.d.ts +103 -0
  38. package/lib/utils/NitroOptionsPanel.js +63 -0
  39. package/lib/utils/NitroSection.d.ts +89 -2
  40. package/lib/utils/NitroSection.js +49 -2
  41. package/nitrogen/generated/android/ReactNativeAutoPlay+autolinking.cmake +1 -0
  42. package/nitrogen/generated/android/c++/JChargingConnector.hpp +79 -0
  43. package/nitrogen/generated/android/c++/JGridImageSize.hpp +64 -0
  44. package/nitrogen/generated/android/c++/JGridTemplateConfig.hpp +7 -1
  45. package/nitrogen/generated/android/c++/JHybridGridTemplateSpec.cpp +4 -0
  46. package/nitrogen/generated/android/c++/JHybridInformationTemplateSpec.cpp +12 -0
  47. package/nitrogen/generated/android/c++/JHybridListTemplateSpec.cpp +12 -0
  48. package/nitrogen/generated/android/c++/JHybridMapTemplateSpec.cpp +78 -3
  49. package/nitrogen/generated/android/c++/JHybridMapTemplateSpec.hpp +2 -1
  50. package/nitrogen/generated/android/c++/JHybridSearchTemplateSpec.cpp +12 -0
  51. package/nitrogen/generated/android/c++/JInformationTemplateConfig.hpp +6 -0
  52. package/nitrogen/generated/android/c++/JListImageType.hpp +67 -0
  53. package/nitrogen/generated/android/c++/JListTemplateConfig.hpp +6 -0
  54. package/nitrogen/generated/android/c++/JNitroChargerLocation.hpp +115 -0
  55. package/nitrogen/generated/android/c++/JNitroChargerOutlet.hpp +82 -0
  56. package/nitrogen/generated/android/c++/JNitroOptionsPanelChargerSection.hpp +112 -0
  57. package/nitrogen/generated/android/c++/JNitroOptionsPanelConfig.hpp +157 -0
  58. package/nitrogen/generated/android/c++/JNitroOptionsPanelGridSection.hpp +102 -0
  59. package/nitrogen/generated/android/c++/JNitroOptionsPanelSection.cpp +30 -0
  60. package/nitrogen/generated/android/c++/JNitroOptionsPanelSection.hpp +129 -0
  61. package/nitrogen/generated/android/c++/JNitroRow.hpp +33 -3
  62. package/nitrogen/generated/android/c++/JNitroSection.hpp +6 -0
  63. package/nitrogen/generated/android/c++/JSearchTemplateConfig.hpp +6 -0
  64. package/nitrogen/generated/android/c++/JWaypointCoordinate.hpp +65 -0
  65. package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/ChargingConnector.kt +30 -0
  66. package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/GridImageSize.kt +25 -0
  67. package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/GridTemplateConfig.kt +9 -4
  68. package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/HybridMapTemplateSpec.kt +5 -1
  69. package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/ListImageType.kt +26 -0
  70. package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroChargerLocation.kt +90 -0
  71. package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroChargerOutlet.kt +70 -0
  72. package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroOptionsPanelChargerSection.kt +61 -0
  73. package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroOptionsPanelConfig.kt +61 -0
  74. package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroOptionsPanelGridSection.kt +56 -0
  75. package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroOptionsPanelSection.kt +75 -0
  76. package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/NitroRow.kt +36 -6
  77. package/nitrogen/generated/android/kotlin/com/margelo/nitro/swe/iternio/reactnativeautoplay/WaypointCoordinate.kt +61 -0
  78. package/nitrogen/generated/ios/ReactNativeAutoPlay-Swift-Cxx-Bridge.hpp +174 -0
  79. package/nitrogen/generated/ios/ReactNativeAutoPlay-Swift-Cxx-Umbrella.hpp +27 -0
  80. package/nitrogen/generated/ios/c++/HybridGridTemplateSpecSwift.hpp +3 -0
  81. package/nitrogen/generated/ios/c++/HybridInformationTemplateSpecSwift.hpp +9 -0
  82. package/nitrogen/generated/ios/c++/HybridListTemplateSpecSwift.hpp +9 -0
  83. package/nitrogen/generated/ios/c++/HybridMapTemplateSpecSwift.hpp +47 -1
  84. package/nitrogen/generated/ios/c++/HybridSearchTemplateSpecSwift.hpp +9 -0
  85. package/nitrogen/generated/ios/swift/ChargingConnector.swift +68 -0
  86. package/nitrogen/generated/ios/swift/GridImageSize.swift +48 -0
  87. package/nitrogen/generated/ios/swift/GridTemplateConfig.swift +12 -1
  88. package/nitrogen/generated/ios/swift/HybridMapTemplateSpec.swift +2 -1
  89. package/nitrogen/generated/ios/swift/HybridMapTemplateSpec_cxx.swift +31 -4
  90. package/nitrogen/generated/ios/swift/ListImageType.swift +52 -0
  91. package/nitrogen/generated/ios/swift/NitroChargerLocation.swift +161 -0
  92. package/nitrogen/generated/ios/swift/NitroChargerOutlet.swift +65 -0
  93. package/nitrogen/generated/ios/swift/NitroOptionsPanelChargerSection.swift +64 -0
  94. package/nitrogen/generated/ios/swift/NitroOptionsPanelConfig.swift +94 -0
  95. package/nitrogen/generated/ios/swift/NitroOptionsPanelGridSection.swift +53 -0
  96. package/nitrogen/generated/ios/swift/NitroOptionsPanelSection.swift +32 -0
  97. package/nitrogen/generated/ios/swift/NitroRow.swift +81 -1
  98. package/nitrogen/generated/ios/swift/WaypointCoordinate.swift +52 -0
  99. package/nitrogen/generated/shared/c++/ChargingConnector.hpp +104 -0
  100. package/nitrogen/generated/shared/c++/GridImageSize.hpp +84 -0
  101. package/nitrogen/generated/shared/c++/GridTemplateConfig.hpp +8 -1
  102. package/nitrogen/generated/shared/c++/HybridMapTemplateSpec.cpp +1 -0
  103. package/nitrogen/generated/shared/c++/HybridMapTemplateSpec.hpp +5 -1
  104. package/nitrogen/generated/shared/c++/ListImageType.hpp +88 -0
  105. package/nitrogen/generated/shared/c++/NitroChargerLocation.hpp +131 -0
  106. package/nitrogen/generated/shared/c++/NitroChargerOutlet.hpp +98 -0
  107. package/nitrogen/generated/shared/c++/NitroOptionsPanelChargerSection.hpp +98 -0
  108. package/nitrogen/generated/shared/c++/NitroOptionsPanelConfig.hpp +107 -0
  109. package/nitrogen/generated/shared/c++/NitroOptionsPanelGridSection.hpp +91 -0
  110. package/nitrogen/generated/shared/c++/NitroRow.hpp +39 -2
  111. package/nitrogen/generated/shared/c++/WaypointCoordinate.hpp +91 -0
  112. package/package.json +1 -1
  113. package/src/Constants.ts +9 -0
  114. package/src/index.ts +1 -0
  115. package/src/specs/MapTemplate.nitro.ts +3 -1
  116. package/src/templates/GridTemplate.ts +38 -3
  117. package/src/templates/InformationTemplate.ts +60 -29
  118. package/src/templates/ListTemplate.ts +27 -55
  119. package/src/templates/MapTemplate.ts +60 -2
  120. package/src/templates/MessageTemplate.ts +71 -24
  121. package/src/utils/NitroOptionsPanel.ts +218 -0
  122. package/src/utils/NitroSection.ts +160 -5
@@ -5,7 +5,7 @@ import type {
5
5
  AutoImage,
6
6
  BaseMapTemplateConfig,
7
7
  CustomActionButtonAndroid,
8
- HeaderActionsAndroid,
8
+ HeaderActions,
9
9
  TextButton,
10
10
  } from '..';
11
11
  import { HybridAutoPlay } from '../hybrid/HybridAutoPlay';
@@ -14,6 +14,7 @@ import type { AutoText } from '../types/Text';
14
14
  import { type NitroAction, NitroActionUtil } from '../utils/NitroAction';
15
15
  import { type NitroImage, NitroImageUtil } from '../utils/NitroImage';
16
16
  import { NitroMapButton } from '../utils/NitroMapButton';
17
+ import type { PanelActionsIos, PanelHeaderActions } from './MapTemplate';
17
18
  import type { NitroBaseMapTemplateConfig, NitroTemplateConfig, TemplateConfig } from './Template';
18
19
 
19
20
  const HybridMessageTemplate =
@@ -23,6 +24,7 @@ export interface NitroMessageTemplateConfig extends TemplateConfig {
23
24
  headerActions?: Array<NitroAction>;
24
25
  /**
25
26
  * @namespace Android title shown on header
27
+ * @namespace iOS title shown when mapConfig is set, otherwise it isn't supported
26
28
  */
27
29
  title?: AutoText;
28
30
  message: AutoText;
@@ -31,41 +33,86 @@ export interface NitroMessageTemplateConfig extends TemplateConfig {
31
33
  mapConfig?: NitroBaseMapTemplateConfig;
32
34
  }
33
35
 
34
- export type MessageTemplateConfig = Omit<
36
+ type MessageActionsAndroid<T> =
37
+ | [CustomActionButtonAndroid<T>]
38
+ | [CustomActionButtonAndroid<T>, CustomActionButtonAndroid<T>];
39
+
40
+ type MessageTemplateBaseConfig = Omit<
35
41
  NitroMessageTemplateConfig,
36
42
  'headerActions' | 'image' | 'mapConfig' | 'actions'
37
43
  > & {
38
44
  /**
39
45
  * action buttons, usually at the top right on Android
40
- * @namespace Android
46
+ * @namespace iOS `ios` only takes effect once this renders as a CPMapPanel (`mapConfig` set,
47
+ * iOS 27+) and is applied to the root map template's nav bar — without `mapConfig` (or below
48
+ * iOS 27, where `mapConfig` is a no-op) this template is a full-screen `CPAlertTemplate`, which
49
+ * has no nav bar at all, so `ios` is silently unused there.
41
50
  */
42
- headerActions?: HeaderActionsAndroid<MessageTemplate>;
51
+ headerActions?: HeaderActions<MessageTemplate>;
43
52
  /**
44
53
  * image shown at the top of the message on Android
45
54
  * @namespace Android
46
55
  */
47
56
  image?: AutoImage;
48
- /**
49
- * If mapConfig is defined, it will use a MapWithContentTemplate with the current template. This results in a MessageTemplate with a map in background. No actions need to be specified, can be empty object.
50
- * @namespace Android
51
- */
52
- mapConfig?: BaseMapTemplateConfig<MessageTemplate>;
53
-
54
- /**
55
- * @namespace Android up to 2 buttons of type TextButton, TextAndImageButton or ImageButton
56
- * @namespace iOS - up to 3 buttons of type TextButton
57
- */
58
- actions?: {
59
- android?:
60
- | [CustomActionButtonAndroid<MessageTemplate>]
61
- | [CustomActionButtonAndroid<MessageTemplate>, CustomActionButtonAndroid<MessageTemplate>];
62
- ios?:
63
- | [TextButton<MessageTemplate>, TextButton<MessageTemplate>, TextButton<MessageTemplate>]
64
- | [TextButton<MessageTemplate>, TextButton<MessageTemplate>]
65
- | [TextButton<MessageTemplate>];
66
- };
67
57
  };
68
58
 
59
+ /**
60
+ * `actions`/`mapConfig` are a discriminated union — `mapConfig` restricts `actions.ios` to at
61
+ * most one `TextButton` plus one icon-only `ImageButton` (a `CPMapPanel` can't show more), vs. up
62
+ * to three `TextButton`s otherwise. If your `mapConfig` value comes from a variable/prop rather
63
+ * than an inline literal, TS can't narrow which branch applies — assign it to a local `const` and
64
+ * branch with `if (mapConfig) { ... } else { ... }` into two separate constructor calls instead
65
+ * of passing it straight through to one.
66
+ */
67
+ export type MessageTemplateConfig = MessageTemplateBaseConfig &
68
+ (
69
+ | {
70
+ mapConfig?: undefined;
71
+
72
+ /**
73
+ * @namespace Android up to 2 buttons of type TextButton, TextAndImageButton or ImageButton
74
+ * @namespace iOS - up to 3 buttons of type TextButton
75
+ */
76
+ actions?: {
77
+ android?: MessageActionsAndroid<MessageTemplate>;
78
+ ios?:
79
+ | [
80
+ TextButton<MessageTemplate>,
81
+ TextButton<MessageTemplate>,
82
+ TextButton<MessageTemplate>,
83
+ ]
84
+ | [TextButton<MessageTemplate>, TextButton<MessageTemplate>]
85
+ | [TextButton<MessageTemplate>];
86
+ };
87
+ }
88
+ | {
89
+ /**
90
+ * If mapConfig is defined, it will use a MapWithContentTemplate with the current
91
+ * template. This results in a MessageTemplate with a map in background. No actions
92
+ * need to be specified, can be empty object.
93
+ * @namespace Android - uses MapWithContentTemplate
94
+ * @namespace iOS - renders as a CPMapPanel on the current root map template (iOS 27+),
95
+ * trading the usual full-screen modal alert presentation for panel content;
96
+ * `headerActions` here is Android-only — on iOS this template's own `headerActions` are
97
+ * applied to the root map template's nav bar instead, since CarPlay has no separate
98
+ * header for the map behind a panel.
99
+ */
100
+ mapConfig: Omit<BaseMapTemplateConfig<MessageTemplate>, 'headerActions'> & {
101
+ headerActions?: PanelHeaderActions<MessageTemplate>;
102
+ };
103
+
104
+ /**
105
+ * @namespace Android up to 2 buttons of type TextButton, TextAndImageButton or ImageButton
106
+ * @namespace iOS - the panel can only show one TextButton plus one optional icon-only
107
+ * ImageButton, see PanelActionsIos
108
+ */
109
+ actions?: {
110
+ android?: MessageActionsAndroid<MessageTemplate>;
111
+ ios?: PanelActionsIos<MessageTemplate>;
112
+ };
113
+ }
114
+ );
115
+
69
116
  /**
70
117
  * This template is always pushed on top and will stay on top until it is popped.
71
118
  * Other templates being pushed will end up below this one on the stack.
@@ -86,7 +133,7 @@ export class MessageTemplate {
86
133
  const nitroConfig: NitroMessageTemplateConfig & NitroTemplateConfig = {
87
134
  ...rest,
88
135
  id: this.id,
89
- headerActions: NitroActionUtil.convert(this.template, { android: headerActions }),
136
+ headerActions: NitroActionUtil.convert(this.template, headerActions),
90
137
  image: NitroImageUtil.convert(image),
91
138
  actions: platformActions,
92
139
  mapConfig: mapConfig
@@ -0,0 +1,218 @@
1
+ import type { ImageButton, TextButton } from '../types/Button';
2
+ import type { AutoImage } from '../types/Image';
3
+ import type { AutoText, Distance } from '../types/Text';
4
+ import type { DurationWithTimeZone } from '../types/Trip';
5
+ import { type NitroAction, NitroActionUtil } from './NitroAction';
6
+ import { type GridButton, type NitroGridButton, NitroGridUtil } from './NitroGrid';
7
+ import { type NitroImage, NitroImageUtil } from './NitroImage';
8
+ import {
9
+ type DefaultRow,
10
+ type MultiSection,
11
+ type NitroSection,
12
+ NitroSectionUtil,
13
+ type RadioRow,
14
+ type TextRow,
15
+ type ToggleRow,
16
+ type WaypointCoordinate,
17
+ type WaypointRow,
18
+ } from './NitroSection';
19
+
20
+ export type OptionsPanelGridSection<T> = {
21
+ type: 'grid';
22
+ title?: string;
23
+ buttons: Array<GridButton<T>>;
24
+ };
25
+
26
+ export type ChargingConnector =
27
+ | 'ccs1'
28
+ | 'ccs2'
29
+ | 'j1772'
30
+ | 'chaDeMo'
31
+ | 'mennekes'
32
+ | 'gbtDC'
33
+ | 'gbtAC'
34
+ | 'nacsDC'
35
+ | 'nacsAC';
36
+
37
+ /**
38
+ * one outlet (`CPChargingStationConnection`) on a charging station.
39
+ */
40
+ export type ChargerOutlet<T> = {
41
+ connector: ChargingConnector;
42
+ voltage: number;
43
+ /** converted to megawatts on the native side above 1000 kW */
44
+ powerKw: number;
45
+ onPress?: (template: T) => void;
46
+ };
47
+
48
+ /**
49
+ * the charging station's own location, shown as an additional `CPMapTemplateWaypoint` item in
50
+ * the charger section, alongside its outlets.
51
+ */
52
+ export type ChargerLocation<T> = {
53
+ /**
54
+ * the item's own label — distinct from the section's `title`, which is already shown as the
55
+ * section header, so this defaults to blank rather than repeating it.
56
+ */
57
+ name?: string;
58
+ /** newline-separated address lines, most-preferred first */
59
+ address?: string;
60
+ coordinate: WaypointCoordinate;
61
+ travelEstimates: {
62
+ distance: Distance;
63
+ duration: DurationWithTimeZone;
64
+ /**
65
+ * by default travel estimates are not shown
66
+ * setting this to true add another row showing CPTravelEstimates
67
+ */
68
+ visible?: boolean;
69
+ };
70
+ image?: AutoImage;
71
+ onPress?: (template: T) => void;
72
+ };
73
+
74
+ /**
75
+ * charger section having n-ChargerOutlet
76
+ */
77
+ export type OptionsPanelChargerSection<T> = {
78
+ type: 'charger';
79
+ title?: string;
80
+ outlets: Array<ChargerOutlet<T>>;
81
+ location?: ChargerLocation<T>;
82
+ };
83
+
84
+ export type OptionsPanelListSection<T> = {
85
+ type: 'list';
86
+ title?: string;
87
+ items: Array<DefaultRow<T> | ToggleRow<T> | TextRow | WaypointRow<T>> | Array<RadioRow<T>>;
88
+ };
89
+
90
+ export type OptionsPanelSection<T> =
91
+ | OptionsPanelListSection<T>
92
+ | OptionsPanelGridSection<T>
93
+ | OptionsPanelChargerSection<T>;
94
+
95
+ export type OptionsPanelConfig<T> = {
96
+ title?: AutoText;
97
+ sections: Array<OptionsPanelSection<T>>;
98
+ actions?: [TextButton<T>] | [TextButton<T>, ImageButton<T>];
99
+ };
100
+
101
+ export type NitroOptionsPanelGridSection = {
102
+ title?: string;
103
+ buttons: Array<NitroGridButton>;
104
+ };
105
+
106
+ export type NitroChargerOutlet = {
107
+ connector: ChargingConnector;
108
+ voltage: number;
109
+ powerKw: number;
110
+ onPress?: () => void;
111
+ };
112
+
113
+ export type NitroChargerLocation = {
114
+ name?: string;
115
+ address?: string;
116
+ coordinate: WaypointCoordinate;
117
+ distance: Distance;
118
+ duration: DurationWithTimeZone;
119
+ visible?: boolean;
120
+ image?: NitroImage;
121
+ onPress?: () => void;
122
+ };
123
+
124
+ export type NitroOptionsPanelChargerSection = {
125
+ title?: string;
126
+ outlets: Array<NitroChargerOutlet>;
127
+ location?: NitroChargerLocation;
128
+ };
129
+
130
+ export type NitroOptionsPanelSection =
131
+ | NitroSection
132
+ | NitroOptionsPanelGridSection
133
+ | NitroOptionsPanelChargerSection;
134
+
135
+ export type NitroOptionsPanelConfig = {
136
+ title?: AutoText;
137
+ sections: Array<NitroOptionsPanelSection>;
138
+ actions?: Array<NitroAction>;
139
+ };
140
+
141
+ const isRadioSection = <T>(
142
+ items: OptionsPanelListSection<T>['items']
143
+ ): items is Array<RadioRow<T>> => {
144
+ return items.length > 0 && items.every((item) => item.type === 'radio');
145
+ };
146
+
147
+ const convertSection = <T>(
148
+ template: T,
149
+ section: OptionsPanelSection<T>
150
+ ): NitroOptionsPanelSection => {
151
+ if (section.type === 'grid') {
152
+ return {
153
+ title: section.title,
154
+ buttons: NitroGridUtil.convert(template, section.buttons),
155
+ };
156
+ }
157
+
158
+ if (section.type === 'charger') {
159
+ const { location } = section;
160
+
161
+ return {
162
+ title: section.title,
163
+ outlets: section.outlets.map((outlet) => ({
164
+ connector: outlet.connector,
165
+ voltage: outlet.voltage,
166
+ powerKw: outlet.powerKw,
167
+ onPress: outlet.onPress ? () => outlet.onPress?.(template) : undefined,
168
+ })),
169
+ location: location
170
+ ? {
171
+ name: location.name,
172
+ address: location.address,
173
+ coordinate: location.coordinate,
174
+ distance: location.travelEstimates.distance,
175
+ duration: location.travelEstimates.duration,
176
+ visible: location.travelEstimates.visible,
177
+ image: NitroImageUtil.convert(location.image),
178
+ onPress: location.onPress ? () => location.onPress?.(template) : undefined,
179
+ }
180
+ : undefined,
181
+ };
182
+ }
183
+
184
+ const { items, title = '' } = section;
185
+
186
+ const multiSection: MultiSection<T> = isRadioSection(items)
187
+ ? { type: 'radio', title, items }
188
+ : {
189
+ type: 'default',
190
+ title,
191
+ items,
192
+ };
193
+
194
+ const [nitroSection] = NitroSectionUtil.convert(template, [multiSection]) ?? [];
195
+
196
+ if (nitroSection == null) {
197
+ throw new Error('converting sections failed');
198
+ }
199
+
200
+ return nitroSection;
201
+ };
202
+
203
+ const convert = <T>(
204
+ template: T,
205
+ config?: OptionsPanelConfig<T>
206
+ ): NitroOptionsPanelConfig | undefined => {
207
+ if (config == null) {
208
+ return undefined;
209
+ }
210
+
211
+ return {
212
+ title: config.title,
213
+ sections: config.sections.map((section) => convertSection(template, section)),
214
+ actions: NitroActionUtil.convert(template, config.actions),
215
+ };
216
+ };
217
+
218
+ export const NitroOptionsPanelUtil = { convert };
@@ -1,7 +1,95 @@
1
- import type { DefaultRow, RadioRow, Section, TextRow, ToggleRow } from '../templates/ListTemplate';
2
- import type { AutoText } from '../types/Text';
1
+ import type { ListImageType } from '../templates/ListTemplate';
2
+ import type { AutoImage } from '../types/Image';
3
+ import { type AutoText, type Distance, TextPlaceholders } from '../types/Text';
4
+ import type { DurationWithTimeZone } from '../types/Trip';
3
5
  import { type NitroImage, NitroImageUtil } from './NitroImage';
4
6
 
7
+ type BaseRow = {
8
+ title: AutoText;
9
+ enabled?: boolean;
10
+ image?: AutoImage;
11
+ /** @namespace Android */
12
+ imageType?: ListImageType;
13
+ };
14
+
15
+ export type DefaultRow<T> = BaseRow & {
16
+ type: 'default';
17
+ /**
18
+ * adds a chevron at the end of the row
19
+ */
20
+ browsable?: boolean;
21
+ onPress: (template: T) => void;
22
+ detailedText?: AutoText;
23
+ };
24
+
25
+ export type ToggleRow<T> = BaseRow & {
26
+ type: 'toggle';
27
+ checked: boolean;
28
+ onPress: (template: T, checked: boolean) => void;
29
+ };
30
+
31
+ export type RadioRow<T> = BaseRow & {
32
+ type: 'radio';
33
+ onPress: (template: T) => void;
34
+ selected?: boolean;
35
+ };
36
+
37
+ export type TextRow = BaseRow & {
38
+ type: 'text';
39
+ detailedText?: AutoText;
40
+ };
41
+
42
+ export type WaypointCoordinate = {
43
+ latitude: number;
44
+ longitude: number;
45
+ altitude?: number;
46
+ };
47
+
48
+ /**
49
+ * a point of interest, e.g. a charger's location. Renders as a `CPMapTemplateWaypoint` item
50
+ * (showing `title` as the name, `address` as the address, and the travel estimate for reaching
51
+ * it) when part of a `CPMapPanel` (iOS 27+); falls back to a plain row using `title`/`address`
52
+ * as the detail line everywhere else (including Android, which has no equivalent concept).
53
+ */
54
+ export type WaypointRow<T> = BaseRow & {
55
+ type: 'waypoint';
56
+ /**
57
+ * add one of {@link TextPlaceholders} to add travelEstimates
58
+ * prefer travelEstimates.visible over that on iOS 27+
59
+ */
60
+ address?: string;
61
+ coordinate: WaypointCoordinate;
62
+ travelEstimates: {
63
+ distance: Distance;
64
+ duration: DurationWithTimeZone;
65
+ /**
66
+ * by default travel estimates are not shown
67
+ * setting this to true adds another row showing CPTravelEstimates
68
+ * @namespace iOS 27+
69
+ */
70
+ visible?: boolean;
71
+ };
72
+ onPress?: (template: T) => void;
73
+ };
74
+
75
+ export type MultiSection<T> =
76
+ | {
77
+ type: 'default';
78
+ title: string;
79
+ items: Array<DefaultRow<T> | ToggleRow<T> | TextRow | WaypointRow<T>>;
80
+ }
81
+ | {
82
+ type: 'radio';
83
+ title: string;
84
+ items: Array<RadioRow<T>>;
85
+ };
86
+
87
+ export type SingleSection<T> = {
88
+ [K in MultiSection<T> as K['type']]: Omit<K, 'title' | 'detailedText'>;
89
+ }[MultiSection<T>['type']];
90
+
91
+ export type Section<T> = Array<MultiSection<T>> | SingleSection<T>;
92
+
5
93
  type NitroSectionType = 'default' | 'radio';
6
94
 
7
95
  export type NitroRow = {
@@ -10,9 +98,20 @@ export type NitroRow = {
10
98
  browsable?: boolean;
11
99
  enabled: boolean;
12
100
  image?: NitroImage;
101
+ imageType?: ListImageType;
13
102
  checked?: boolean;
14
103
  onPress?: (checked?: boolean) => void;
15
104
  selected?: boolean;
105
+ coordinate?: WaypointCoordinate;
106
+ distance?: Distance;
107
+ duration?: DurationWithTimeZone;
108
+ /**
109
+ * only meaningful in panel context (iOS 27+, `mapConfig` set) — adds a sibling `CPMapPanelItem`
110
+ * showing `distance`/`duration` as a native `CPTravelEstimates` row. Non-panel/Android rendering
111
+ * relies purely on `title`/`detailedText` opting in via `TextPlaceholders` instead.
112
+ */
113
+ travelEstimatesVisible?: boolean;
114
+ address?: string;
16
115
  };
17
116
 
18
117
  export type NitroSection = {
@@ -63,13 +162,63 @@ const convert = <T>(template: T, sections?: Section<T>): Array<NitroSection> | u
63
162
  ];
64
163
  };
65
164
 
165
+ /**
166
+ * `AutoText.distance`/`.duration` only take effect if `text` actually uses the matching
167
+ * `TextPlaceholders` marker — populate whichever of those the text references, leaving the rest
168
+ * of `text` untouched, so a title/address that doesn't opt in via a placeholder is unaffected.
169
+ */
170
+ const withTravelEstimates = (
171
+ text: AutoText,
172
+ distance: Distance,
173
+ durationSeconds: number
174
+ ): AutoText => {
175
+ const hasDistance = text.text.includes(TextPlaceholders.Distance);
176
+ const hasDuration = text.text.includes(TextPlaceholders.Duration);
177
+
178
+ if (!hasDistance && !hasDuration) {
179
+ return text;
180
+ }
181
+
182
+ return {
183
+ ...text,
184
+ distance: hasDistance ? distance : text.distance,
185
+ duration: hasDuration ? durationSeconds : text.duration,
186
+ };
187
+ };
188
+
66
189
  const convertRow = <T>(
67
190
  template: T,
68
- item: DefaultRow<T> | RadioRow<T> | ToggleRow<T> | TextRow
191
+ item: DefaultRow<T> | RadioRow<T> | ToggleRow<T> | TextRow | WaypointRow<T>
69
192
  ): NitroRow => {
70
- const { title, type, enabled = true, image } = item;
193
+ const { type, enabled = true, image, imageType } = item;
194
+
195
+ // `WaypointRow` has no `detailedText` of its own — `address` doubles as the detail line
196
+ // whenever this falls back to a plain row (non-panel context, Android).
197
+ let title = item.title;
198
+ let detailedText =
199
+ item.type === 'waypoint'
200
+ ? item.address != null
201
+ ? { text: item.address }
202
+ : undefined
203
+ : 'detailedText' in item
204
+ ? item.detailedText
205
+ : undefined;
206
+
207
+ // Opt-in only: a title/address using `{distance}`/`{duration}` gets those values filled in,
208
+ // via the same substitution `AutoText` already does everywhere else — no separate "estimate"
209
+ // UI element needed for a row embedded in a list alongside other rows. Deliberately NOT gated
210
+ // on `travelEstimates.visible` — that flag only controls the panel-only sibling
211
+ // `CPTravelEstimates` item (see `Parser.swift`'s `travelEstimatesVisible` check), which has no
212
+ // bearing on the non-panel/Android fallback text this feeds; a placeholder in the text is
213
+ // already its own explicit opt-in regardless of `visible`.
214
+ if (item.type === 'waypoint') {
215
+ const { distance, duration } = item.travelEstimates;
216
+ title = withTravelEstimates(title, distance, duration.seconds);
217
+ if (detailedText != null) {
218
+ detailedText = withTravelEstimates(detailedText, distance, duration.seconds);
219
+ }
220
+ }
71
221
 
72
- const detailedText = 'detailedText' in item ? item.detailedText : undefined;
73
222
  const selected = type === 'radio' ? (item.selected ?? false) : undefined;
74
223
 
75
224
  const onTogglePress = item.type === 'toggle' ? item.onPress : undefined;
@@ -93,10 +242,16 @@ const convertRow = <T>(
93
242
  detailedText,
94
243
  enabled,
95
244
  image: NitroImageUtil.convert(image),
245
+ imageType,
96
246
  title,
97
247
  checked: type === 'toggle' ? item.checked : undefined,
98
248
  onPress,
99
249
  selected,
250
+ coordinate: item.type === 'waypoint' ? item.coordinate : undefined,
251
+ distance: item.type === 'waypoint' ? item.travelEstimates.distance : undefined,
252
+ duration: item.type === 'waypoint' ? item.travelEstimates.duration : undefined,
253
+ travelEstimatesVisible: item.type === 'waypoint' ? item.travelEstimates.visible : undefined,
254
+ address: item.type === 'waypoint' ? item.address : undefined,
100
255
  };
101
256
  };
102
257