react-native-audio-browser 0.2.0-next.13 → 0.2.0-next.14

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 (48) hide show
  1. package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +3 -0
  2. package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +44 -0
  3. package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +0 -2
  4. package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +1 -1
  5. package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +21 -22
  6. package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +8 -5
  7. package/android/src/main/res/values/strings.xml +37 -2
  8. package/android/src/test/java/com/audiobrowser/TestFixtures.kt +1 -2
  9. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +9 -0
  10. package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +2 -1
  11. package/ios/Browser/JsonModels.swift +0 -5
  12. package/ios/Extension/Section+Copying.swift +1 -3
  13. package/ios/HybridAudioBrowser.swift +2 -1
  14. package/ios/Model/NitroTypeStubs.swift +0 -1
  15. package/ios/Tests/ResolvedTrackCopyingTests.swift +2 -2
  16. package/lib/commonjs/features/browser-config.js +1 -0
  17. package/lib/commonjs/features/browser-config.js.map +1 -1
  18. package/lib/module/features/browser-config.js +1 -0
  19. package/lib/module/features/browser-config.js.map +1 -1
  20. package/lib/typescript/src/features/browser-config.d.ts.map +1 -1
  21. package/lib/typescript/src/types/browser-native.d.ts +2 -1
  22. package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
  23. package/lib/typescript/src/types/browser-nodes.d.ts +14 -9
  24. package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
  25. package/lib/typescript/src/types/browser.d.ts +36 -0
  26. package/lib/typescript/src/types/browser.d.ts.map +1 -1
  27. package/nitrogen/generated/android/AudioBrowserOnLoad.cpp +2 -0
  28. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__string__.hpp +102 -0
  29. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +1 -0
  30. package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +14 -1
  31. package/nitrogen/generated/android/c++/JSection.hpp +1 -5
  32. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__string__.kt +80 -0
  33. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +7 -4
  34. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Section.kt +2 -5
  35. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.cpp +9 -0
  36. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +50 -0
  37. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__string__.swift +54 -0
  38. package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +48 -1
  39. package/nitrogen/generated/ios/swift/Section.swift +1 -19
  40. package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +6 -2
  41. package/nitrogen/generated/shared/c++/Section.hpp +1 -5
  42. package/package.json +1 -1
  43. package/src/features/browser-config.ts +1 -0
  44. package/src/types/browser-native.ts +5 -1
  45. package/src/types/browser-nodes.ts +14 -9
  46. package/src/types/browser.ts +38 -0
  47. package/website/guide/android-auto.md +1 -1
  48. package/website/guide/browser.md +19 -19
@@ -18,19 +18,13 @@ public extension Section {
18
18
  /**
19
19
  * Create a new instance of `Section`.
20
20
  */
21
- init(title: String?, subtitle: String?, style: SectionStyle?, path: String?, children: [Track]) {
21
+ init(title: String?, style: SectionStyle?, path: String?, children: [Track]) {
22
22
  self.init({ () -> bridge.std__optional_std__string_ in
23
23
  if let __unwrappedValue = title {
24
24
  return bridge.create_std__optional_std__string_(std.string(__unwrappedValue))
25
25
  } else {
26
26
  return .init()
27
27
  }
28
- }(), { () -> bridge.std__optional_std__string_ in
29
- if let __unwrappedValue = subtitle {
30
- return bridge.create_std__optional_std__string_(std.string(__unwrappedValue))
31
- } else {
32
- return .init()
33
- }
34
28
  }(), { () -> bridge.std__optional_SectionStyle_ in
35
29
  if let __unwrappedValue = style {
36
30
  return bridge.create_std__optional_SectionStyle_(__unwrappedValue)
@@ -64,18 +58,6 @@ public extension Section {
64
58
  }()
65
59
  }
66
60
 
67
- @inline(__always)
68
- var subtitle: String? {
69
- return { () -> String? in
70
- if bridge.has_value_std__optional_std__string_(self.__subtitle) {
71
- let __unwrapped = bridge.get_std__optional_std__string_(self.__subtitle)
72
- return String(__unwrapped)
73
- } else {
74
- return nil
75
- }
76
- }()
77
- }
78
-
79
61
  @inline(__always)
80
62
  var style: SectionStyle? {
81
63
  return self.__style.value
@@ -78,16 +78,17 @@ namespace margelo::nitro::audiobrowser {
78
78
  std::optional<bool> singleTrack SWIFT_PRIVATE;
79
79
  std::optional<std::function<std::shared_ptr<Promise<std::shared_ptr<Promise<void>>>>(const TrackLoadEvent& /* event */)>> handleTrackLoad SWIFT_PRIVATE;
80
80
  std::optional<bool> androidControllerOfflineError SWIFT_PRIVATE;
81
+ std::optional<std::function<std::shared_ptr<Promise<std::string>>()>> viewMoreTitle SWIFT_PRIVATE;
81
82
  std::optional<std::string> carPlayLoadingTitle SWIFT_PRIVATE;
82
83
  std::optional<std::function<std::shared_ptr<Promise<std::optional<std::string>>>(const Track& /* track */)>> resolveAlbumPath SWIFT_PRIVATE;
83
84
  std::optional<std::function<std::shared_ptr<Promise<std::optional<FormattedNavigationError>>>(const FormatNavigationErrorParams& /* params */)>> formatNavigationError SWIFT_PRIVATE;
84
85
 
85
86
  public:
86
87
  NativeBrowserConfiguration() = default;
87
- explicit NativeBrowserConfiguration(std::optional<std::string> path, std::optional<TransformableRequestConfig> request, std::optional<std::function<std::shared_ptr<Promise<std::shared_ptr<Promise<TransformableRequestConfig>>>>()>> requestResolver, std::optional<TransformableRequestConfig> browse, std::optional<std::function<std::shared_ptr<Promise<std::shared_ptr<Promise<TransformableRequestConfig>>>>()>> browseResolver, std::optional<MediaRequestConfig> media, std::optional<ArtworkRequestConfig> artwork, std::optional<ArtworkRequestConfig> nowPlayingArtwork, std::optional<std::vector<NativeRouteEntry>> routes, std::optional<bool> singleTrack, std::optional<std::function<std::shared_ptr<Promise<std::shared_ptr<Promise<void>>>>(const TrackLoadEvent& /* event */)>> handleTrackLoad, std::optional<bool> androidControllerOfflineError, std::optional<std::string> carPlayLoadingTitle, std::optional<std::function<std::shared_ptr<Promise<std::optional<std::string>>>(const Track& /* track */)>> resolveAlbumPath, std::optional<std::function<std::shared_ptr<Promise<std::optional<FormattedNavigationError>>>(const FormatNavigationErrorParams& /* params */)>> formatNavigationError): path(path), request(request), requestResolver(requestResolver), browse(browse), browseResolver(browseResolver), media(media), artwork(artwork), nowPlayingArtwork(nowPlayingArtwork), routes(routes), singleTrack(singleTrack), handleTrackLoad(handleTrackLoad), androidControllerOfflineError(androidControllerOfflineError), carPlayLoadingTitle(carPlayLoadingTitle), resolveAlbumPath(resolveAlbumPath), formatNavigationError(formatNavigationError) {}
88
+ explicit NativeBrowserConfiguration(std::optional<std::string> path, std::optional<TransformableRequestConfig> request, std::optional<std::function<std::shared_ptr<Promise<std::shared_ptr<Promise<TransformableRequestConfig>>>>()>> requestResolver, std::optional<TransformableRequestConfig> browse, std::optional<std::function<std::shared_ptr<Promise<std::shared_ptr<Promise<TransformableRequestConfig>>>>()>> browseResolver, std::optional<MediaRequestConfig> media, std::optional<ArtworkRequestConfig> artwork, std::optional<ArtworkRequestConfig> nowPlayingArtwork, std::optional<std::vector<NativeRouteEntry>> routes, std::optional<bool> singleTrack, std::optional<std::function<std::shared_ptr<Promise<std::shared_ptr<Promise<void>>>>(const TrackLoadEvent& /* event */)>> handleTrackLoad, std::optional<bool> androidControllerOfflineError, std::optional<std::function<std::shared_ptr<Promise<std::string>>()>> viewMoreTitle, std::optional<std::string> carPlayLoadingTitle, std::optional<std::function<std::shared_ptr<Promise<std::optional<std::string>>>(const Track& /* track */)>> resolveAlbumPath, std::optional<std::function<std::shared_ptr<Promise<std::optional<FormattedNavigationError>>>(const FormatNavigationErrorParams& /* params */)>> formatNavigationError): path(path), request(request), requestResolver(requestResolver), browse(browse), browseResolver(browseResolver), media(media), artwork(artwork), nowPlayingArtwork(nowPlayingArtwork), routes(routes), singleTrack(singleTrack), handleTrackLoad(handleTrackLoad), androidControllerOfflineError(androidControllerOfflineError), viewMoreTitle(viewMoreTitle), carPlayLoadingTitle(carPlayLoadingTitle), resolveAlbumPath(resolveAlbumPath), formatNavigationError(formatNavigationError) {}
88
89
 
89
90
  public:
90
- // NativeBrowserConfiguration is not equatable because these properties are not equatable: request, requestResolver, browse, browseResolver, media, artwork, nowPlayingArtwork, routes, handleTrackLoad, resolveAlbumPath, formatNavigationError
91
+ // NativeBrowserConfiguration is not equatable because these properties are not equatable: request, requestResolver, browse, browseResolver, media, artwork, nowPlayingArtwork, routes, handleTrackLoad, viewMoreTitle, resolveAlbumPath, formatNavigationError
91
92
  };
92
93
 
93
94
  } // namespace margelo::nitro::audiobrowser
@@ -112,6 +113,7 @@ namespace margelo::nitro {
112
113
  JSIConverter<std::optional<bool>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "singleTrack"))),
113
114
  JSIConverter<std::optional<std::function<std::shared_ptr<Promise<std::shared_ptr<Promise<void>>>>(const margelo::nitro::audiobrowser::TrackLoadEvent&)>>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "handleTrackLoad"))),
114
115
  JSIConverter<std::optional<bool>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "androidControllerOfflineError"))),
116
+ JSIConverter<std::optional<std::function<std::shared_ptr<Promise<std::string>>()>>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "viewMoreTitle"))),
115
117
  JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "carPlayLoadingTitle"))),
116
118
  JSIConverter<std::optional<std::function<std::shared_ptr<Promise<std::optional<std::string>>>(const margelo::nitro::audiobrowser::Track&)>>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "resolveAlbumPath"))),
117
119
  JSIConverter<std::optional<std::function<std::shared_ptr<Promise<std::optional<margelo::nitro::audiobrowser::FormattedNavigationError>>>(const margelo::nitro::audiobrowser::FormatNavigationErrorParams&)>>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "formatNavigationError")))
@@ -131,6 +133,7 @@ namespace margelo::nitro {
131
133
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "singleTrack"), JSIConverter<std::optional<bool>>::toJSI(runtime, arg.singleTrack));
132
134
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "handleTrackLoad"), JSIConverter<std::optional<std::function<std::shared_ptr<Promise<std::shared_ptr<Promise<void>>>>(const margelo::nitro::audiobrowser::TrackLoadEvent&)>>>::toJSI(runtime, arg.handleTrackLoad));
133
135
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "androidControllerOfflineError"), JSIConverter<std::optional<bool>>::toJSI(runtime, arg.androidControllerOfflineError));
136
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "viewMoreTitle"), JSIConverter<std::optional<std::function<std::shared_ptr<Promise<std::string>>()>>>::toJSI(runtime, arg.viewMoreTitle));
134
137
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "carPlayLoadingTitle"), JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.carPlayLoadingTitle));
135
138
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "resolveAlbumPath"), JSIConverter<std::optional<std::function<std::shared_ptr<Promise<std::optional<std::string>>>(const margelo::nitro::audiobrowser::Track&)>>>::toJSI(runtime, arg.resolveAlbumPath));
136
139
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "formatNavigationError"), JSIConverter<std::optional<std::function<std::shared_ptr<Promise<std::optional<margelo::nitro::audiobrowser::FormattedNavigationError>>>(const margelo::nitro::audiobrowser::FormatNavigationErrorParams&)>>>::toJSI(runtime, arg.formatNavigationError));
@@ -156,6 +159,7 @@ namespace margelo::nitro {
156
159
  if (!JSIConverter<std::optional<bool>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "singleTrack")))) return false;
157
160
  if (!JSIConverter<std::optional<std::function<std::shared_ptr<Promise<std::shared_ptr<Promise<void>>>>(const margelo::nitro::audiobrowser::TrackLoadEvent&)>>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "handleTrackLoad")))) return false;
158
161
  if (!JSIConverter<std::optional<bool>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "androidControllerOfflineError")))) return false;
162
+ if (!JSIConverter<std::optional<std::function<std::shared_ptr<Promise<std::string>>()>>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "viewMoreTitle")))) return false;
159
163
  if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "carPlayLoadingTitle")))) return false;
160
164
  if (!JSIConverter<std::optional<std::function<std::shared_ptr<Promise<std::optional<std::string>>>(const margelo::nitro::audiobrowser::Track&)>>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "resolveAlbumPath")))) return false;
161
165
  if (!JSIConverter<std::optional<std::function<std::shared_ptr<Promise<std::optional<margelo::nitro::audiobrowser::FormattedNavigationError>>>(const margelo::nitro::audiobrowser::FormatNavigationErrorParams&)>>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "formatNavigationError")))) return false;
@@ -47,14 +47,13 @@ namespace margelo::nitro::audiobrowser {
47
47
  struct Section final {
48
48
  public:
49
49
  std::optional<std::string> title SWIFT_PRIVATE;
50
- std::optional<std::string> subtitle SWIFT_PRIVATE;
51
50
  std::optional<SectionStyle> style SWIFT_PRIVATE;
52
51
  std::optional<std::string> path SWIFT_PRIVATE;
53
52
  std::vector<Track> children SWIFT_PRIVATE;
54
53
 
55
54
  public:
56
55
  Section() = default;
57
- explicit Section(std::optional<std::string> title, std::optional<std::string> subtitle, std::optional<SectionStyle> style, std::optional<std::string> path, std::vector<Track> children): title(title), subtitle(subtitle), style(style), path(path), children(children) {}
56
+ explicit Section(std::optional<std::string> title, std::optional<SectionStyle> style, std::optional<std::string> path, std::vector<Track> children): title(title), style(style), path(path), children(children) {}
58
57
 
59
58
  public:
60
59
  friend bool operator==(const Section& lhs, const Section& rhs) = default;
@@ -71,7 +70,6 @@ namespace margelo::nitro {
71
70
  jsi::Object obj = arg.asObject(runtime);
72
71
  return margelo::nitro::audiobrowser::Section(
73
72
  JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "title"))),
74
- JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "subtitle"))),
75
73
  JSIConverter<std::optional<margelo::nitro::audiobrowser::SectionStyle>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "style"))),
76
74
  JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "path"))),
77
75
  JSIConverter<std::vector<margelo::nitro::audiobrowser::Track>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "children")))
@@ -80,7 +78,6 @@ namespace margelo::nitro {
80
78
  static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::audiobrowser::Section& arg) {
81
79
  jsi::Object obj(runtime);
82
80
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "title"), JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.title));
83
- obj.setProperty(runtime, PropNameIDCache::get(runtime, "subtitle"), JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.subtitle));
84
81
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "style"), JSIConverter<std::optional<margelo::nitro::audiobrowser::SectionStyle>>::toJSI(runtime, arg.style));
85
82
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "path"), JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.path));
86
83
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "children"), JSIConverter<std::vector<margelo::nitro::audiobrowser::Track>>::toJSI(runtime, arg.children));
@@ -95,7 +92,6 @@ namespace margelo::nitro {
95
92
  return false;
96
93
  }
97
94
  if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "title")))) return false;
98
- if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "subtitle")))) return false;
99
95
  if (!JSIConverter<std::optional<margelo::nitro::audiobrowser::SectionStyle>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "style")))) return false;
100
96
  if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "path")))) return false;
101
97
  if (!JSIConverter<std::vector<margelo::nitro::audiobrowser::Track>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "children")))) return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-audio-browser",
3
- "version": "0.2.0-next.13",
3
+ "version": "0.2.0-next.14",
4
4
  "description": "react-native-audio-browser is a react native package built with Nitro",
5
5
  "keywords": [
6
6
  "react-native",
@@ -289,6 +289,7 @@ export function toNativeConfig(
289
289
  singleTrack: config.singleTrack,
290
290
  handleTrackLoad: config.handleTrackLoad,
291
291
  androidControllerOfflineError: config.androidControllerOfflineError,
292
+ viewMoreTitle: config.viewMoreTitle,
292
293
  carPlayLoadingTitle: config.carPlayLoadingTitle,
293
294
  resolveAlbumPath: config.resolveAlbumPath,
294
295
  formatNavigationError: config.formatNavigationError
@@ -14,7 +14,8 @@ import type {
14
14
  HandleTrackLoadCallback,
15
15
  MediaRequestConfig,
16
16
  SearchSourceCallback,
17
- TransformableRequestConfig
17
+ TransformableRequestConfig,
18
+ ViewMoreTitleCallback
18
19
  } from './browser'
19
20
  import type { ResolvedTrack } from './browser-nodes'
20
21
 
@@ -73,6 +74,9 @@ export interface NativeBrowserConfiguration {
73
74
  singleTrack?: boolean
74
75
  handleTrackLoad?: HandleTrackLoadCallback
75
76
  androidControllerOfflineError?: boolean
77
+ // Title of the "view more" row appended to a pathed section where the header
78
+ // can't be tapped (Android Auto). Absent = native's "View more" default.
79
+ viewMoreTitle?: ViewMoreTitleCallback
76
80
 
77
81
  // CarPlay options
78
82
  carPlayLoadingTitle?: string
@@ -293,11 +293,6 @@ export type TrackArtwork = string | ArtworkVariants
293
293
  export interface Section {
294
294
  /** Header text. Absent = headerless group. */
295
295
  title?: string
296
- /**
297
- * Secondary line for the section's navigation surface — e.g. the label
298
- * of the "view all" link a tile section gets on Android Auto.
299
- */
300
- subtitle?: string
301
296
  /**
302
297
  * Presentation, separated from content. Each surface renders the
303
298
  * declared layout's nearest supported form; on CarPlay
@@ -308,10 +303,20 @@ export interface Section {
308
303
  */
309
304
  style?: SectionStyle
310
305
  /**
311
- * Navigation target for the section header / "view all" surface. A
312
- * tile section's header tap (CarPlay) and appended "view all" link
313
- * (Android Auto) navigate here. Absent = a self-contained section:
314
- * the header is a plain label, not a link.
306
+ * Navigation target for the section's "view all" affordance — the
307
+ * fuller page this section is a teaser for. Absent = a self-contained
308
+ * section: the header is a plain label, not a link.
309
+ *
310
+ * The surface picks the form (ADR 0010), and each carries the copy
311
+ * itself — a `path` never adds words to the section:
312
+ *
313
+ * - **CarPlay** — a tile section's row draws a disclosure chevron and
314
+ * the whole row navigates. A list section renders no affordance:
315
+ * CarPlay has no "see all" idiom for one, and an invented row would
316
+ * spend an item from the template's cap.
317
+ * - **Android Auto** — no header is tappable at any display, so the
318
+ * section gets an appended browsable row, titled by the global
319
+ * `viewMoreTitle` browser option ("View more" by default).
315
320
  */
316
321
  path?: string
317
322
  /** The section's tracks. */
@@ -931,6 +931,41 @@ export type BrowserConfiguration = {
931
931
  */
932
932
  androidControllerOfflineError?: boolean
933
933
 
934
+ /**
935
+ * Titles the "view more" row appended to a `Section` that carries a
936
+ * `Section.path` on surfaces whose section header cannot be tapped —
937
+ * Android Auto, today the only one.
938
+ *
939
+ * Leave it unset and the row is titled "View more" — the library's in-code
940
+ * default, the same arrangement `formatNavigationError` has. Set this to
941
+ * localize it: it is re-resolved once per content generation, so
942
+ * `invalidateAllContent()` after an in-app language switch picks up the new
943
+ * copy without reconfiguring the browser. That is why it is a callback rather
944
+ * than a plain string — an app whose locale is its own, not the system's,
945
+ * cannot express that with a value frozen at configure time.
946
+ *
947
+ * One string for every section by design — the row is a fallback for a
948
+ * surface that cannot draw a link in its header, so per-section wording would
949
+ * be ceremony most consumers never see rendered.
950
+ *
951
+ * For wording that varies by section — "View all 42 albums", "See every
952
+ * episode" — author the link yourself instead: append an ordinary browsable
953
+ * {@link Track} to the section's `children`, titled however you like, and
954
+ * leave `Section.path` unset so this row doesn't also appear. Since the
955
+ * row is Android-only, that branch is an ordinary platform check on the
956
+ * content you emit; CarPlay never sees the extra row.
957
+ *
958
+ * @default 'View more'
959
+ *
960
+ * @example
961
+ * ```typescript
962
+ * viewMoreTitle: () => t('View more')
963
+ * ```
964
+ *
965
+ * @platform android
966
+ */
967
+ viewMoreTitle?: ViewMoreTitleCallback
968
+
934
969
  /**
935
970
  * Title shown (as the list's centered empty state) on CarPlay screens whose
936
971
  * content is still loading — browse destinations while they resolve, and the
@@ -1093,3 +1128,6 @@ export type FormatNavigationErrorCallback = (
1093
1128
  * or `undefined` to do nothing. See `resolveAlbumPath`.
1094
1129
  */
1095
1130
  export type ResolveAlbumPathCallback = (track: Track) => string | undefined
1131
+
1132
+ /** Supplies the title of the appended "view more" row. See `viewMoreTitle`. */
1133
+ export type ViewMoreTitleCallback = () => string
@@ -66,7 +66,7 @@ Android Auto reads a page's layout from the **parent item** that opens it — be
66
66
  ```
67
67
 
68
68
  - **`style.display` on the parent item** — `'list'` (rows) or `'grid'` (tiles). Below the root this is the only layout signal Android Auto honors, and it is only sent when you declare it — leave it off and the page renders as a list. In dev mode the library warns when an all-grid page is opened from a parent item with no declared `display`. (A section's "view all" page needs no declaration of its own: it reuses the section's `style`.)
69
- - **Sections** — declare `sections` on the page to group its tracks under headers; each [`Section`](/api/types/browser-nodes/#section)'s `title` renders as a header above its children, plus a "view all" link built from the section's `path` and `subtitle`.
69
+ - **Sections** — declare `sections` on the page to group its tracks under headers; each [`Section`](/api/types/browser-nodes/#section)'s `title` renders as a header above its children. A section that carries a `path` also gets a browsable "view more" row appended after its children, navigating there. No header is tappable on Android Auto at any `display`, so the row is the only affordance the section's `path` has here; its title comes from the [`viewMoreTitle`](/api/types/browser/#viewmoretitle) browser option, defaulting to `"View more"` — one wording for every section. For per-section copy (`"View all 42 albums"`), leave `path` unset and append your own browsable `Track` to `children` instead, titled however you like; this page's content is Android-only anyway, so CarPlay never sees it.
70
70
 
71
71
  ::: warning AAOS lays out the whole page from the parent item's `display`
72
72
  On AOSP-based Android Automotive units — including mainstream cars' OEM skins — the parent item's `display` is the **only** below-root layout signal honored: the whole page renders as list or grid, uniformly. Per-section `display` differences (a single-line grid above a list, say) render on CarPlay and projected Android Auto, but an AAOS unit shows the page in the single declared layout. There is no way to describe a mixed page there — pick the layout that suits its dominant section, or split the content across pages. `gridWrap: false` also has no single-line container there: the grid wraps, showing more, never less.
@@ -76,7 +76,7 @@ A **page** is a [`ResolvedTrack`](/api/types/browser-nodes/#resolvedtrack): it r
76
76
  }
77
77
  ```
78
78
 
79
- A Section is `{ title?, subtitle?, style?, path?, children }`: `title` renders as a header above its tracks, `style` is a declaration block whose `display` picks the children's layout (`'list'` rows — the default — or `'grid'` artwork tiles; `gridWrap: false` keeps the grid to a single line), and `path` is the navigation target for the header / "view all" surface. See [Presentation](#presentation) for how each surface renders the declarations.
79
+ A Section is `{ title?, style?, path?, children }`: `title` renders as a header above its tracks, `style` is a declaration block whose `display` picks the children's layout (`'list'` rows — the default — or `'grid'` artwork tiles; `gridWrap: false` keeps the grid to a single line), and `path` is the navigation target for the section's "view all" affordance. See [Presentation](#presentation) for how each surface renders the declarations.
80
80
 
81
81
  A flat page doesn't have to spell that out: authoring a plain `children: Track[]` — as most examples in this guide do — is sugar for one untitled section. Resolved output always carries `sections`; `children` is never populated on a resolved page.
82
82
 
@@ -512,24 +512,24 @@ The `code` values: `content-not-found`, `network-error`, `http-error`, `callback
512
512
 
513
513
  Presentation lives in `style` blocks, separate from content, plus a few Track fields and config options. Each surface applies the style properties it supports and ignores the rest — an unsupported property is a no-op, never an error.
514
514
 
515
- | Field | On | Effect | Platform |
516
- | ------------------------------------------------ | ------------------------ | ---------------------------------------------------------------------------- | ------------------- |
517
- | `title` | a section | header above its tracks | all |
518
- | `style: { display: 'grid' }` | a section | wrapping artwork-tile grid | all\* |
519
- | `style: { display: 'grid', gridWrap: false }` | a section | a single line of artwork tiles | all\* |
520
- | `path` (+ `subtitle`) | a section | header / "view all" navigation target | all |
521
- | `style` (same block) | a page | declaration for the whole page — a section overrides it for its own children | all |
522
- | `style: { display: 'grid' }` | a browsable track | the layout of the page it opens — Android Auto reads it from the parent item | Android Auto / AAOS |
523
- | `style: { artworkRendering: 'stencil' }` | a track / section / page | tint monochrome artwork to the surface appearance | CarPlay |
524
- | `style: { imageShape: 'circular' }` | a track / section / page | crop grid-tile artwork circular (people) instead of rounded | CarPlay (iOS 26+) |
525
- | `style: { accessorySymbol: 'lock.fill' }` | a track / section / page | SF Symbol accessory on rows and tiles (`'none'` restores the derived one) | CarPlay |
526
- | `style: { gridTile: 'card' }` | a section / page | the grid's tile kind: `'plain'` (default), `'card'`, or `'condensed'` | CarPlay (iOS 26+) |
527
- | `style: { cardTint: '#1e3a8a' }` (+ `cardImage`) | a track / section / page | card color and image mode (`'background'` fills the card full-height) | CarPlay (iOS 26+) |
528
- | `disabled: true` | a track | unavailable: never plays; grayed where drawable, hidden elsewhere | all |
529
- | `artwork: 'sf:heart.fill'` | any item | SF Symbol icon (supports `?bg=…&fg=…`) | iOS |
530
- | `live: true` | a track | live indicator | iOS |
531
- | `carPlaySiriListButton: 'top' \| 'bottom'` | a page | place the Siri cell on the page | iOS |
532
- | `albumPath` + `resolveAlbumPath` | track + config | make the now-playing album line tappable | CarPlay |
515
+ | Field | On | Effect | Platform |
516
+ | ------------------------------------------------ | ------------------------ | --------------------------------------------------------------------------------- | ------------------- |
517
+ | `title` | a section | header above its tracks | all |
518
+ | `style: { display: 'grid' }` | a section | wrapping artwork-tile grid | all\* |
519
+ | `style: { display: 'grid', gridWrap: false }` | a section | a single line of artwork tiles | all\* |
520
+ | `path` | a section | "view all" target — a tappable header on CarPlay, an appended row on Android Auto | all |
521
+ | `style` (same block) | a page | declaration for the whole page — a section overrides it for its own children | all |
522
+ | `style: { display: 'grid' }` | a browsable track | the layout of the page it opens — Android Auto reads it from the parent item | Android Auto / AAOS |
523
+ | `style: { artworkRendering: 'stencil' }` | a track / section / page | tint monochrome artwork to the surface appearance | CarPlay |
524
+ | `style: { imageShape: 'circular' }` | a track / section / page | crop grid-tile artwork circular (people) instead of rounded | CarPlay (iOS 26+) |
525
+ | `style: { accessorySymbol: 'lock.fill' }` | a track / section / page | SF Symbol accessory on rows and tiles (`'none'` restores the derived one) | CarPlay |
526
+ | `style: { gridTile: 'card' }` | a section / page | the grid's tile kind: `'plain'` (default), `'card'`, or `'condensed'` | CarPlay (iOS 26+) |
527
+ | `style: { cardTint: '#1e3a8a' }` (+ `cardImage`) | a track / section / page | card color and image mode (`'background'` fills the card full-height) | CarPlay (iOS 26+) |
528
+ | `disabled: true` | a track | unavailable: never plays; grayed where drawable, hidden elsewhere | all |
529
+ | `artwork: 'sf:heart.fill'` | any item | SF Symbol icon (supports `?bg=…&fg=…`) | iOS |
530
+ | `live: true` | a track | live indicator | iOS |
531
+ | `carPlaySiriListButton: 'top' \| 'bottom'` | a page | place the Siri cell on the page | iOS |
532
+ | `albumPath` + `resolveAlbumPath` | track + config | make the now-playing album line tappable | CarPlay |
533
533
 
534
534
  ::: info Caveats from the table
535
535
  **`albumPath` requires `album`** — CarPlay renders the tappable line from the album metadata, so without an `album` there is no line to tap. **\*Layouts render each platform's nearest supported form** — a single-line grid (`gridWrap: false`) on CarPlay shows the tiles that fit (roughly eight, width-dependent) and truncates the rest; a wrapping grid takes as many lines as it needs on iOS 26+ and renders as a list before that. Android Auto's grid always wraps, so it shows more, never less. No car surface scrolls horizontally; an app UI typically renders `gridWrap: false` as a horizontal scroller. Truncation is visual only: tapping a tile queues the whole section (see [Playback behavior](#playback-behavior)). Item properties like `artworkRendering`, `imageShape`, and `accessorySymbol` are inherited — `track ?? section ?? page`, per property — so a section-wide value can be overridden per item; `accessorySymbol: 'none'` is the per-item escape from an inherited symbol, and where the accessory draws is the platform's choice per surface (trailing on list rows, leading the title on tiles). `display` is never inherited: it always describes the children of whatever it's set on. On a page or section it lays out that page or section; on a browsable track it declares the layout of the page that track opens — Android Auto needs it there, or that page renders as a list. `gridWrap` is inert unless `display` is `'grid'`. One AAOS caveat: AOSP-based Automotive units honor only the parent item's `display`, so a page mixing grid and list sections renders uniformly there — see [Android Auto → Browse display](/guide/android-auto#browse-display).