@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
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
//
|
|
7
7
|
|
|
8
8
|
import CarPlay
|
|
9
|
+
import CoreLocation
|
|
9
10
|
import ImageIO
|
|
10
11
|
import UIKit
|
|
11
12
|
|
|
@@ -25,9 +26,13 @@ class Parser {
|
|
|
25
26
|
var actions: [CPAlertAction] = []
|
|
26
27
|
|
|
27
28
|
if let alertActions = alertActions {
|
|
29
|
+
// Image-only actions (from the panel-only PanelActionsIos shape) have no title and
|
|
30
|
+
// can't be represented as a CPAlertAction below iOS 27 where panels aren't used.
|
|
28
31
|
alertActions.forEach { alertAction in
|
|
32
|
+
guard let title = alertAction.title else { return }
|
|
33
|
+
|
|
29
34
|
let action = CPAlertAction(
|
|
30
|
-
title:
|
|
35
|
+
title: title,
|
|
31
36
|
style: parseActionAlertStyle(style: alertAction.style),
|
|
32
37
|
handler: { actionHandler in
|
|
33
38
|
alertAction.onPress()
|
|
@@ -64,8 +69,6 @@ class Parser {
|
|
|
64
69
|
if let glypImage = action.image?.glyphImage {
|
|
65
70
|
image = SymbolFont.imageFromNitroImage(
|
|
66
71
|
image: glypImage,
|
|
67
|
-
// this icon is not scaled properly when used as image asset, so we use the plain image, as CP does the correct coloring anyways
|
|
68
|
-
noImageAsset: true,
|
|
69
72
|
traitCollection: traitCollection
|
|
70
73
|
)!
|
|
71
74
|
}
|
|
@@ -220,16 +223,18 @@ class Parser {
|
|
|
220
223
|
{
|
|
221
224
|
guard let actions else { return [] }
|
|
222
225
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
+
// Image-only actions (from the panel-only PanelActionsIos shape) have no title and
|
|
227
|
+
// can't be represented as a CPTextButton below iOS 27 where panels aren't used.
|
|
228
|
+
return actions.compactMap { action in
|
|
229
|
+
guard let title = action.title else { return nil }
|
|
230
|
+
|
|
231
|
+
return CPTextButton(
|
|
232
|
+
title: title,
|
|
226
233
|
textStyle: parseTextButtonStyle(style: action.style),
|
|
227
234
|
handler: { void in
|
|
228
235
|
action.onPress()
|
|
229
236
|
}
|
|
230
237
|
)
|
|
231
|
-
|
|
232
|
-
return button
|
|
233
238
|
}
|
|
234
239
|
}
|
|
235
240
|
|
|
@@ -244,6 +249,63 @@ class Parser {
|
|
|
244
249
|
}
|
|
245
250
|
}
|
|
246
251
|
|
|
252
|
+
/// Read-only equivalent of `parseInformationItems` for the map-panel case, since `CPMapPanelItem` only wraps a `CPListItem`, not a `CPInformationItem`.
|
|
253
|
+
@available(iOS 27.0, *)
|
|
254
|
+
static func parseInformationPanelItems(section: NitroSection) -> [CPMapPanelItem] {
|
|
255
|
+
return section.items.map { item in
|
|
256
|
+
CPMapPanelItem(
|
|
257
|
+
listItem: CPListItem(
|
|
258
|
+
text: parseText(text: item.title),
|
|
259
|
+
detailText: parseText(text: item.detailedText)
|
|
260
|
+
)
|
|
261
|
+
)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/// `actions[0]` becomes the primary text button; `actions[1]`, if present, becomes the icon-only symbol button — `CPMapPanelButtonConfiguration` supports nothing beyond that.
|
|
266
|
+
@available(iOS 27.0, *)
|
|
267
|
+
static func parsePanelButtonConfiguration(
|
|
268
|
+
actions: [NitroAction]?,
|
|
269
|
+
traitCollection: UITraitCollection
|
|
270
|
+
) -> CPMapPanelButtonConfiguration? {
|
|
271
|
+
guard let actions, let primaryAction = actions.first else { return nil }
|
|
272
|
+
|
|
273
|
+
let primaryButton = CPTextButton(
|
|
274
|
+
title: primaryAction.title ?? "",
|
|
275
|
+
textStyle: parseTextButtonStyle(style: primaryAction.style),
|
|
276
|
+
handler: { _ in
|
|
277
|
+
primaryAction.onPress()
|
|
278
|
+
}
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
var symbolButton: CPButton?
|
|
282
|
+
if actions.count > 1,
|
|
283
|
+
let image = parseNitroImage(image: actions[1].image, traitCollection: traitCollection)
|
|
284
|
+
{
|
|
285
|
+
let secondaryAction = actions[1]
|
|
286
|
+
symbolButton = CPButton(
|
|
287
|
+
image: image,
|
|
288
|
+
handler: { _ in
|
|
289
|
+
secondaryAction.onPress()
|
|
290
|
+
}
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/// according to the docs travelEstimates is optional, the intializer just does not know it....
|
|
295
|
+
/// set it and nil it on next line....
|
|
296
|
+
let buttonConfiguration = CPMapPanelButtonConfiguration(
|
|
297
|
+
primaryAction: primaryButton,
|
|
298
|
+
secondaryButton: symbolButton,
|
|
299
|
+
travelEstimates: CPTravelEstimates(
|
|
300
|
+
distanceRemaining: Measurement(value: 0, unit: .astronomicalUnits),
|
|
301
|
+
timeRemaining: 0
|
|
302
|
+
)
|
|
303
|
+
)
|
|
304
|
+
buttonConfiguration.travelEstimates = nil
|
|
305
|
+
|
|
306
|
+
return buttonConfiguration
|
|
307
|
+
}
|
|
308
|
+
|
|
247
309
|
static func parseSearchResults(
|
|
248
310
|
section: NitroSection?,
|
|
249
311
|
traitCollection: UITraitCollection
|
|
@@ -272,6 +334,110 @@ class Parser {
|
|
|
272
334
|
}
|
|
273
335
|
}
|
|
274
336
|
|
|
337
|
+
static func parseListItems(
|
|
338
|
+
section: NitroSection,
|
|
339
|
+
sectionIndex: Int,
|
|
340
|
+
updateSection: @escaping (NitroSection, Int) -> Void,
|
|
341
|
+
traitCollection: UITraitCollection
|
|
342
|
+
) -> [CPListItem] {
|
|
343
|
+
let selectedIndex = section.items.firstIndex { item in
|
|
344
|
+
item.selected == true
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return section.items.enumerated().map { (itemIndex, item) in
|
|
348
|
+
parseListItem(
|
|
349
|
+
item: item,
|
|
350
|
+
itemIndex: itemIndex,
|
|
351
|
+
selectedIndex: selectedIndex,
|
|
352
|
+
section: section,
|
|
353
|
+
sectionIndex: sectionIndex,
|
|
354
|
+
updateSection: updateSection,
|
|
355
|
+
traitCollection: traitCollection
|
|
356
|
+
)
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private static func parseListItem(
|
|
361
|
+
item: NitroRow,
|
|
362
|
+
itemIndex: Int,
|
|
363
|
+
selectedIndex: Int?,
|
|
364
|
+
section: NitroSection,
|
|
365
|
+
sectionIndex: Int,
|
|
366
|
+
updateSection: @escaping (NitroSection, Int) -> Void,
|
|
367
|
+
traitCollection: UITraitCollection
|
|
368
|
+
) -> CPListItem {
|
|
369
|
+
let isSelected =
|
|
370
|
+
section.type == .radio
|
|
371
|
+
&& Int(selectedIndex ?? -1) == itemIndex
|
|
372
|
+
|
|
373
|
+
let toggleImage = item.checked.map { checked in
|
|
374
|
+
UIImage.makeToggleImage(
|
|
375
|
+
enabled: checked,
|
|
376
|
+
maximumImageSize: CPListItem.maximumImageSize,
|
|
377
|
+
traitCollection: traitCollection
|
|
378
|
+
)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
let listItem = CPListItem(
|
|
382
|
+
text: parseText(text: item.title),
|
|
383
|
+
detailText: parseText(text: item.detailedText),
|
|
384
|
+
image: Parser.parseNitroImage(
|
|
385
|
+
image: item.image,
|
|
386
|
+
traitCollection: traitCollection
|
|
387
|
+
),
|
|
388
|
+
accessoryImage: isSelected
|
|
389
|
+
? UIImage.checkmark : toggleImage,
|
|
390
|
+
accessoryType: item.browsable == true
|
|
391
|
+
? .disclosureIndicator : .none
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
listItem.isEnabled = item.enabled
|
|
395
|
+
|
|
396
|
+
listItem.handler = { _item, completion in
|
|
397
|
+
let updatedItems = section.items.enumerated().map { (rowIndex, row) in
|
|
398
|
+
let checked: Bool? =
|
|
399
|
+
if rowIndex == itemIndex, let checked = row.checked {
|
|
400
|
+
!checked
|
|
401
|
+
}
|
|
402
|
+
else { row.checked }
|
|
403
|
+
|
|
404
|
+
let selected: Bool? =
|
|
405
|
+
if section.type == .radio {
|
|
406
|
+
rowIndex == itemIndex
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
nil
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return NitroRow(
|
|
413
|
+
title: row.title,
|
|
414
|
+
detailedText: row.detailedText,
|
|
415
|
+
browsable: row.browsable,
|
|
416
|
+
enabled: row.enabled,
|
|
417
|
+
image: row.image,
|
|
418
|
+
imageType: row.imageType,
|
|
419
|
+
checked: checked,
|
|
420
|
+
onPress: row.onPress,
|
|
421
|
+
selected: selected,
|
|
422
|
+
coordinate: row.coordinate,
|
|
423
|
+
distance: row.distance,
|
|
424
|
+
duration: row.duration,
|
|
425
|
+
travelEstimatesVisible: row.travelEstimatesVisible,
|
|
426
|
+
address: row.address
|
|
427
|
+
)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
let updatedSection = NitroSection(title: section.title, items: updatedItems, type: section.type)
|
|
431
|
+
|
|
432
|
+
updateSection(updatedSection, sectionIndex)
|
|
433
|
+
|
|
434
|
+
item.onPress?(item.checked.map { checked in !checked })
|
|
435
|
+
completion()
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return listItem
|
|
439
|
+
}
|
|
440
|
+
|
|
275
441
|
static func parseSections(
|
|
276
442
|
sections: [NitroSection]?,
|
|
277
443
|
updateSection: @escaping (NitroSection, Int) -> Void,
|
|
@@ -280,81 +446,410 @@ class Parser {
|
|
|
280
446
|
guard let sections else { return [] }
|
|
281
447
|
|
|
282
448
|
return sections.enumerated().map { (sectionIndex, section) in
|
|
283
|
-
let
|
|
284
|
-
|
|
449
|
+
let items = parseListItems(
|
|
450
|
+
section: section,
|
|
451
|
+
sectionIndex: sectionIndex,
|
|
452
|
+
updateSection: updateSection,
|
|
453
|
+
traitCollection: traitCollection
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
return CPListSection(
|
|
457
|
+
items: items,
|
|
458
|
+
header: section.title,
|
|
459
|
+
sectionIndexTitle: nil
|
|
460
|
+
)
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/// Builds the `CPMapPanelItem`s for a list-type section in panel context. Mirrors `parseListItems`, except a row carrying
|
|
465
|
+
/// waypoint data (`coordinate`) becomes a `CPMapTemplateWaypoint`-backed item instead of a `CPListItem`-backed one — used
|
|
466
|
+
/// both by `ListTemplate` panels and the options panel's list sections.
|
|
467
|
+
@available(iOS 27.0, *)
|
|
468
|
+
static func parsePanelItems(
|
|
469
|
+
section: NitroSection,
|
|
470
|
+
sectionIndex: Int,
|
|
471
|
+
updateSection: @escaping (NitroSection, Int) -> Void,
|
|
472
|
+
traitCollection: UITraitCollection
|
|
473
|
+
) -> [CPMapPanelItem] {
|
|
474
|
+
let selectedIndex = section.items.firstIndex { item in
|
|
475
|
+
item.selected == true
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return section.items.enumerated().flatMap { (itemIndex, item) -> [CPMapPanelItem] in
|
|
479
|
+
if let coordinate = item.coordinate {
|
|
480
|
+
let image = parseWaypointImage(item.image, traitCollection: traitCollection)
|
|
481
|
+
let onPress = item.onPress
|
|
482
|
+
let waypointItem = parseWaypointPanelItem(
|
|
483
|
+
name: parseText(text: item.title),
|
|
484
|
+
address: item.address,
|
|
485
|
+
coordinate: coordinate,
|
|
486
|
+
distance: item.distance,
|
|
487
|
+
duration: item.duration,
|
|
488
|
+
image: image,
|
|
489
|
+
onPress: { onPress?(nil) }
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
guard item.travelEstimatesVisible == true, let distance = item.distance,
|
|
493
|
+
let duration = item.duration
|
|
494
|
+
else {
|
|
495
|
+
return [waypointItem]
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
let travelEstimates = CPTravelEstimates(
|
|
499
|
+
distanceRemaining: parseDistance(distance: distance),
|
|
500
|
+
timeRemaining: duration.seconds
|
|
501
|
+
)
|
|
502
|
+
let travelEstimatesItem = CPMapPanelItem(travelEstimates: travelEstimates) {
|
|
503
|
+
_,
|
|
504
|
+
completion in
|
|
505
|
+
completion()
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return [waypointItem, travelEstimatesItem]
|
|
285
509
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
510
|
+
|
|
511
|
+
let listItem = parseListItem(
|
|
512
|
+
item: item,
|
|
513
|
+
itemIndex: itemIndex,
|
|
514
|
+
selectedIndex: selectedIndex,
|
|
515
|
+
section: section,
|
|
516
|
+
sectionIndex: sectionIndex,
|
|
517
|
+
updateSection: updateSection,
|
|
518
|
+
traitCollection: traitCollection
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
return [CPMapPanelItem(listItem: listItem)]
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
@available(iOS 27.0, *)
|
|
526
|
+
static func parseMapPanelSections(
|
|
527
|
+
sections: [NitroSection]?,
|
|
528
|
+
updateSection: @escaping (NitroSection, Int) -> Void,
|
|
529
|
+
traitCollection: UITraitCollection
|
|
530
|
+
) -> [CPMapPanelSection] {
|
|
531
|
+
guard let sections else { return [] }
|
|
532
|
+
|
|
533
|
+
return sections.enumerated().map { (sectionIndex, section) in
|
|
534
|
+
let items = parsePanelItems(
|
|
535
|
+
section: section,
|
|
536
|
+
sectionIndex: sectionIndex,
|
|
537
|
+
updateSection: updateSection,
|
|
538
|
+
traitCollection: traitCollection
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
return CPMapPanelSection(
|
|
542
|
+
title: section.title,
|
|
543
|
+
items: items
|
|
544
|
+
)
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/// Builds sections for `CPNavigationSession.optionsPanel`. Each section is independently a full list (via `parsePanelItems`,
|
|
549
|
+
/// radio included — a list row carrying waypoint data becomes a `CPMapTemplateWaypoint` item), a full grid, or a charging
|
|
550
|
+
/// station's outlets — `sectionIndex` addresses this array; only list sections call `updateSection`.
|
|
551
|
+
///
|
|
552
|
+
/// The case order below (`.first` = list, `.second` = grid, `.third` = charger) matches whatever nitrogen assigned the last
|
|
553
|
+
/// time `yarn specs` ran, not necessarily the declaration order of `NitroOptionsPanelSection` in TS — check
|
|
554
|
+
/// `NitroOptionsPanelSection.swift` after regenerating.
|
|
555
|
+
@available(iOS 27.0, *)
|
|
556
|
+
static func parseOptionsPanelSections(
|
|
557
|
+
sections: [NitroOptionsPanelSection]?,
|
|
558
|
+
updateSection: @escaping (NitroSection, Int) -> Void,
|
|
559
|
+
traitCollection: UITraitCollection
|
|
560
|
+
) -> [CPMapPanelSection] {
|
|
561
|
+
guard let sections else { return [] }
|
|
562
|
+
|
|
563
|
+
return sections.enumerated().map { (sectionIndex, section) in
|
|
564
|
+
switch section {
|
|
565
|
+
case .first(let listSection):
|
|
566
|
+
let items = parsePanelItems(
|
|
567
|
+
section: listSection,
|
|
568
|
+
sectionIndex: sectionIndex,
|
|
569
|
+
updateSection: updateSection,
|
|
570
|
+
traitCollection: traitCollection
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
return CPMapPanelSection(
|
|
574
|
+
title: listSection.title,
|
|
575
|
+
items: items
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
case .second(let gridSection):
|
|
579
|
+
let buttons = parseGridButtons(
|
|
580
|
+
buttons: gridSection.buttons,
|
|
581
|
+
traitCollection: traitCollection
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
return CPMapPanelSection(
|
|
585
|
+
title: gridSection.title,
|
|
586
|
+
items: [CPMapPanelItem(gridButtons: buttons)]
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
case .third(let chargerSection):
|
|
590
|
+
var items = chargerSection.outlets.map { outlet -> CPMapPanelItem in
|
|
591
|
+
let connection = CPChargingStationConnection(
|
|
592
|
+
connector: parseChargingConnector(outlet.connector),
|
|
593
|
+
voltage: Measurement(value: outlet.voltage, unit: .volts),
|
|
594
|
+
power: parsePower(kilowatts: outlet.powerKw)
|
|
295
595
|
)
|
|
596
|
+
|
|
597
|
+
let onPress = outlet.onPress
|
|
598
|
+
return CPMapPanelItem(chargingStationConnection: connection) { _, completion in
|
|
599
|
+
onPress?()
|
|
600
|
+
completion()
|
|
601
|
+
}
|
|
296
602
|
}
|
|
297
603
|
|
|
298
|
-
let
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
604
|
+
if let location = chargerSection.location {
|
|
605
|
+
let onPress = location.onPress
|
|
606
|
+
let image = parseWaypointImage(location.image, traitCollection: traitCollection)
|
|
607
|
+
items.insert(
|
|
608
|
+
parseWaypointPanelItem(
|
|
609
|
+
name: location.name,
|
|
610
|
+
address: location.address,
|
|
611
|
+
coordinate: location.coordinate,
|
|
612
|
+
distance: location.distance,
|
|
613
|
+
duration: location.duration,
|
|
614
|
+
image: image,
|
|
615
|
+
onPress: { onPress?() }
|
|
616
|
+
),
|
|
617
|
+
at: 0
|
|
618
|
+
)
|
|
310
619
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
let selected: Bool? =
|
|
323
|
-
if section.type == .radio {
|
|
324
|
-
rowIndex == itemIndex
|
|
325
|
-
}
|
|
326
|
-
else {
|
|
327
|
-
nil
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
return NitroRow(
|
|
331
|
-
title: row.title,
|
|
332
|
-
detailedText: row.detailedText,
|
|
333
|
-
browsable: row.browsable,
|
|
334
|
-
enabled: row.enabled,
|
|
335
|
-
image: row.image,
|
|
336
|
-
checked: checked,
|
|
337
|
-
onPress: row.onPress,
|
|
338
|
-
selected: selected
|
|
620
|
+
if location.visible == true {
|
|
621
|
+
let travelEstimates = CPTravelEstimates(
|
|
622
|
+
distanceRemaining: parseDistance(distance: location.distance),
|
|
623
|
+
timeRemaining: location.duration.seconds
|
|
624
|
+
)
|
|
625
|
+
items.insert(
|
|
626
|
+
CPMapPanelItem(travelEstimates: travelEstimates) { _, completion in
|
|
627
|
+
completion()
|
|
628
|
+
},
|
|
629
|
+
at: 1
|
|
339
630
|
)
|
|
340
631
|
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return CPMapPanelSection(
|
|
635
|
+
title: chargerSection.title,
|
|
636
|
+
items: items
|
|
637
|
+
)
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
341
641
|
|
|
342
|
-
|
|
642
|
+
/// Shared image handling for a waypoint panel item (`WaypointRow`, `ChargerLocation`) — a glyph image is rendered
|
|
643
|
+
/// directly at `CPNavigationAlert.maximumAvatarImageSize`, since `CPMapPanelItem`'s waypoint image has no documented
|
|
644
|
+
/// size of its own; anything else falls back to the regular image conversion.
|
|
645
|
+
@available(iOS 27.0, *)
|
|
646
|
+
private static func parseWaypointImage(
|
|
647
|
+
_ image: ImageProtocol?,
|
|
648
|
+
traitCollection: UITraitCollection
|
|
649
|
+
) -> UIImage? {
|
|
650
|
+
if let glyphImage = image?.glyphImage {
|
|
651
|
+
return SymbolFont.imageFromNitroImage(
|
|
652
|
+
image: glyphImage,
|
|
653
|
+
// iOS 27 makes the image overflow
|
|
654
|
+
// adjusting it with displayScale gives it the proper size but causes some blur
|
|
655
|
+
size: CPNavigationAlert.maximumAvatarImageSize.width / traitCollection.displayScale,
|
|
656
|
+
traitCollection: traitCollection
|
|
657
|
+
)
|
|
658
|
+
}
|
|
343
659
|
|
|
344
|
-
|
|
660
|
+
return parseNitroImage(image: image, traitCollection: traitCollection)
|
|
661
|
+
}
|
|
345
662
|
|
|
346
|
-
|
|
347
|
-
|
|
663
|
+
/// Builds a `CPMapPanelItem` wrapping a `CPMapTemplateWaypoint` — used both for a list row carrying waypoint data
|
|
664
|
+
/// (`WaypointRow`) and for a charging station's own location (`ChargerLocation`).
|
|
665
|
+
@available(iOS 27.0, *)
|
|
666
|
+
private static func parseWaypointPanelItem(
|
|
667
|
+
name: String?,
|
|
668
|
+
address: String?,
|
|
669
|
+
coordinate: WaypointCoordinate,
|
|
670
|
+
distance: Distance?,
|
|
671
|
+
duration: DurationWithTimeZone?,
|
|
672
|
+
image: UIImage?,
|
|
673
|
+
onPress: @escaping () -> Void
|
|
674
|
+
) -> CPMapPanelItem {
|
|
675
|
+
let nameVariants = name.map { [$0] } ?? []
|
|
676
|
+
|
|
677
|
+
let navigationWaypoint = CPNavigationWaypoint(
|
|
678
|
+
centerPoint: CPLocationCoordinate3D(
|
|
679
|
+
latitude: coordinate.latitude,
|
|
680
|
+
longitude: coordinate.longitude,
|
|
681
|
+
altitude: coordinate.altitude ?? CLLocationDistanceMax
|
|
682
|
+
),
|
|
683
|
+
locationThreshold: nil,
|
|
684
|
+
nameVariants: nameVariants,
|
|
685
|
+
addressVariants: address.map { [$0] } ?? [],
|
|
686
|
+
entryPoints: [],
|
|
687
|
+
timeZone: duration.flatMap { TimeZone(identifier: $0.timezone) }
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
// `CPMapTemplateWaypoint.travelEstimates` isn't optional and isn't displayed by the
|
|
691
|
+
// waypoint item itself (the visible estimate is the separate sibling `CPMapPanelItem`
|
|
692
|
+
// above, added only when the caller opts in) — a negative value is Apple's documented
|
|
693
|
+
// way to say "unavailable" (renders as "--") for whenever this happens to be read.
|
|
694
|
+
let travelEstimates =
|
|
695
|
+
if let distance, let duration {
|
|
696
|
+
CPTravelEstimates(
|
|
697
|
+
distanceRemaining: parseDistance(distance: distance),
|
|
698
|
+
timeRemaining: duration.seconds
|
|
699
|
+
)
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
CPTravelEstimates(
|
|
703
|
+
distanceRemaining: Measurement(value: -1, unit: .meters),
|
|
704
|
+
timeRemaining: -1
|
|
705
|
+
)
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
let mapTemplateWaypoint = CPMapTemplateWaypoint(
|
|
709
|
+
waypoint: navigationWaypoint,
|
|
710
|
+
travelEstimates: travelEstimates
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
return CPMapPanelItem(mapTemplateWaypoint: mapTemplateWaypoint, image: image) { _, completion in
|
|
714
|
+
onPress()
|
|
715
|
+
completion()
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
@available(iOS 27.0, *)
|
|
720
|
+
private static func parseChargingConnector(
|
|
721
|
+
_ connector: ChargingConnector
|
|
722
|
+
) -> CPChargingStationConnection.Connector {
|
|
723
|
+
switch connector {
|
|
724
|
+
case .ccs1: return .ccs1
|
|
725
|
+
case .ccs2: return .ccs2
|
|
726
|
+
case .j1772: return .j1772
|
|
727
|
+
case .chademo: return .chaDeMo
|
|
728
|
+
case .mennekes: return .mennekes
|
|
729
|
+
case .gbtdc: return .gbtDC
|
|
730
|
+
case .gbtac: return .gbtAC
|
|
731
|
+
case .nacsdc: return .nacsDC
|
|
732
|
+
case .nacsac: return .nacsAC
|
|
733
|
+
default: return .ccs2
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/// values above 1000 kW are shown in megawatts instead, since nobody wants to read "1500 kW" on a charger card.
|
|
738
|
+
private static func parsePower(kilowatts: Double) -> Measurement<UnitPower> {
|
|
739
|
+
let measurement = Measurement(value: kilowatts, unit: UnitPower.kilowatts)
|
|
740
|
+
return kilowatts >= 1000 ? measurement.converted(to: .megawatts) : measurement
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/// `onPanButtonPress` is called instead of `button.onPress` for `.pan`-typed buttons, since panning belongs to the `CPMapTemplate`, not the button.
|
|
744
|
+
static func parseMapButtons(
|
|
745
|
+
mapButtons: [NitroMapButton],
|
|
746
|
+
onPanButtonPress: @escaping () -> Void
|
|
747
|
+
) -> [CPMapButton] {
|
|
748
|
+
guard let traitCollection = SceneStore.getRootTraitCollection() else {
|
|
749
|
+
return []
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
return mapButtons.map { button in
|
|
753
|
+
if let glyphImage = button.image.glyphImage,
|
|
754
|
+
let icon = SymbolFont.imageFromNitroImage(
|
|
755
|
+
image: glyphImage,
|
|
756
|
+
traitCollection: traitCollection
|
|
757
|
+
)
|
|
758
|
+
{
|
|
759
|
+
return CPMapButton(image: icon) { _ in
|
|
760
|
+
if button.type == .pan {
|
|
761
|
+
onPanButtonPress()
|
|
762
|
+
return
|
|
763
|
+
}
|
|
764
|
+
button.onPress?()
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
if let assetImage = button.image.assetImage,
|
|
768
|
+
let icon = Parser.parseAssetImage(
|
|
769
|
+
assetImage: assetImage,
|
|
770
|
+
traitCollection: traitCollection
|
|
771
|
+
)
|
|
772
|
+
{
|
|
773
|
+
return CPMapButton(image: icon) { _ in
|
|
774
|
+
if button.type == .pan {
|
|
775
|
+
onPanButtonPress()
|
|
776
|
+
return
|
|
777
|
+
}
|
|
778
|
+
button.onPress?()
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
if let remoteImage = button.image.remoteImage,
|
|
782
|
+
let icon = Parser.parseRemoteImage(
|
|
783
|
+
remoteImage: remoteImage,
|
|
784
|
+
traitCollection: traitCollection
|
|
785
|
+
)
|
|
786
|
+
{
|
|
787
|
+
return CPMapButton(image: icon) { _ in
|
|
788
|
+
if button.type == .pan {
|
|
789
|
+
onPanButtonPress()
|
|
790
|
+
return
|
|
791
|
+
}
|
|
792
|
+
button.onPress?()
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
return CPMapButton { _ in
|
|
797
|
+
if button.type == .pan {
|
|
798
|
+
onPanButtonPress()
|
|
799
|
+
return
|
|
348
800
|
}
|
|
801
|
+
button.onPress?()
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
349
805
|
|
|
350
|
-
|
|
806
|
+
static func parseGridButtons(
|
|
807
|
+
buttons: [NitroGridButton],
|
|
808
|
+
traitCollection: UITraitCollection
|
|
809
|
+
) -> [CPGridButton] {
|
|
810
|
+
let gridButtonHeight: CGFloat
|
|
811
|
+
|
|
812
|
+
if #available(iOS 26.0, *) {
|
|
813
|
+
gridButtonHeight = CPGridTemplate.maximumGridButtonImageSize.height
|
|
814
|
+
}
|
|
815
|
+
else {
|
|
816
|
+
gridButtonHeight = 44
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
return buttons.compactMap { button in
|
|
820
|
+
var image: UIImage?
|
|
821
|
+
|
|
822
|
+
if let glyphImage = button.image.glyphImage {
|
|
823
|
+
image = SymbolFont.imageFromNitroImage(
|
|
824
|
+
image: glyphImage,
|
|
825
|
+
size: gridButtonHeight,
|
|
826
|
+
traitCollection: traitCollection
|
|
827
|
+
)
|
|
351
828
|
}
|
|
352
829
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
830
|
+
if let assetImage = button.image.assetImage {
|
|
831
|
+
image = Parser.parseAssetImage(
|
|
832
|
+
assetImage: assetImage,
|
|
833
|
+
traitCollection: traitCollection
|
|
834
|
+
)
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if let remoteImage = button.image.remoteImage {
|
|
838
|
+
image = Parser.parseRemoteImage(
|
|
839
|
+
remoteImage: remoteImage,
|
|
840
|
+
traitCollection: traitCollection
|
|
841
|
+
)
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
guard let image = image else { return nil }
|
|
845
|
+
guard let title = Parser.parseText(text: button.title) else { return nil }
|
|
846
|
+
|
|
847
|
+
return CPGridButton(
|
|
848
|
+
titleVariants: [title],
|
|
849
|
+
image: image
|
|
850
|
+
) { _ in
|
|
851
|
+
button.onPress()
|
|
852
|
+
}
|
|
358
853
|
}
|
|
359
854
|
}
|
|
360
855
|
|