react-native-audio-browser 0.2.0-next.10 → 0.2.0-next.12

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 (345) hide show
  1. package/README.md +4 -4
  2. package/android/build.gradle +6 -5
  3. package/android/gradle.properties +1 -1
  4. package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +110 -25
  5. package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +254 -188
  6. package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +23 -3
  7. package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +77 -58
  8. package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +68 -26
  9. package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +49 -0
  10. package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +9 -7
  11. package/android/src/main/java/com/audiobrowser/extension/TappedTrack.kt +20 -0
  12. package/android/src/main/java/com/audiobrowser/extension/TrackIdentity.kt +15 -0
  13. package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +120 -67
  14. package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +21 -24
  15. package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +93 -73
  16. package/android/src/main/java/com/audiobrowser/player/Player.kt +48 -30
  17. package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
  18. package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +46 -5
  19. package/android/src/main/java/com/audiobrowser/tls/AiaChasingExtendedTrustManager.kt +86 -27
  20. package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +58 -14
  21. package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +20 -6
  22. package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +249 -86
  23. package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +44 -40
  24. package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +28 -23
  25. package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +35 -0
  26. package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +45 -53
  27. package/android/src/test/java/com/audiobrowser/TestFixtures.kt +69 -30
  28. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
  29. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +6 -0
  30. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +345 -8
  31. package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +38 -0
  32. package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +1 -1
  33. package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +91 -0
  34. package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -53
  35. package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +87 -0
  36. package/android/src/test/java/com/audiobrowser/extension/TappedTrackTest.kt +43 -0
  37. package/android/src/test/java/com/audiobrowser/extension/TrackIdentityTest.kt +41 -0
  38. package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +1 -2
  39. package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +42 -2
  40. package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +55 -0
  41. package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +152 -8
  42. package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +218 -10
  43. package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherCallSiteTest.kt +322 -0
  44. package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +41 -16
  45. package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +161 -0
  46. package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +7 -7
  47. package/ios/Browser/BrowserConfig.swift +102 -5
  48. package/ios/Browser/BrowserManager+URLResolution.swift +18 -14
  49. package/ios/Browser/BrowserManager.swift +131 -143
  50. package/ios/Browser/BrowserPathHelper.swift +44 -44
  51. package/ios/Browser/JsonModels.swift +187 -106
  52. package/ios/Browser/SearchDrillIn.swift +6 -6
  53. package/ios/Browser/SectionPresentation.swift +34 -0
  54. package/ios/Browser/SectionScope.swift +58 -24
  55. package/ios/Browser/StyleResolver.swift +47 -0
  56. package/ios/Browser/TabBarEntries.swift +2 -2
  57. package/ios/Browser/TrackIdTemplate.swift +37 -0
  58. package/ios/CarPlay/CarPlayArtworkResolver.swift +8 -7
  59. package/ios/CarPlay/CarPlayController.swift +105 -67
  60. package/ios/CarPlay/CarPlayImageLoader.swift +38 -18
  61. package/ios/CarPlay/CarPlayListItemFactory.swift +217 -84
  62. package/ios/CarPlay/CarPlayNowPlayingManager.swift +15 -12
  63. package/ios/Extension/Capability+RemoteCommand.swift +1 -1
  64. package/ios/Extension/PlayerCapabilities+Favorite.swift +1 -15
  65. package/ios/Extension/ResolvedTrack+Copying.swift +11 -15
  66. package/ios/Extension/Section+Copying.swift +31 -0
  67. package/ios/Extension/Track+Copying.swift +6 -12
  68. package/ios/Extension/Track+Identity.swift +18 -0
  69. package/ios/HybridAudioBrowser.swift +113 -35
  70. package/ios/Model/NitroTypeStubs.swift +64 -23
  71. package/ios/Model/PlayerUpdateOptions.swift +7 -0
  72. package/ios/Player/NowPlayingUpdater.swift +1 -1
  73. package/ios/Player/PlaybackCoordinator.swift +2 -2
  74. package/ios/Player/RetryManager.swift +4 -3
  75. package/ios/Tests/BrowserPathHelperTests.swift +50 -20
  76. package/ios/Tests/CarPlayArtworkResolverTests.swift +24 -41
  77. package/ios/Tests/JsonModelsTests.swift +75 -1
  78. package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
  79. package/ios/Tests/QueueManagerTests.swift +4 -4
  80. package/ios/Tests/ResolvedTrackCopyingTests.swift +37 -35
  81. package/ios/Tests/SearchDrillInTests.swift +16 -16
  82. package/ios/Tests/SectionPresentationTests.swift +61 -0
  83. package/ios/Tests/SectionScopeTests.swift +87 -55
  84. package/ios/Tests/StyleResolverTests.swift +67 -0
  85. package/ios/Tests/TabBarEntriesTests.swift +18 -18
  86. package/ios/Tests/TrackIdTemplateTests.swift +67 -0
  87. package/ios/Tests/TrackIdentityTests.swift +20 -0
  88. package/ios/Tests/TrackSelectorTests.swift +168 -33
  89. package/ios/TrackSelector.swift +45 -30
  90. package/ios/Util/ArtworkImageFetcher.swift +16 -1
  91. package/lib/commonjs/AudioBrowser.js +11 -0
  92. package/lib/commonjs/AudioBrowser.js.map +1 -1
  93. package/lib/commonjs/features/browser-config.js +11 -8
  94. package/lib/commonjs/features/browser-config.js.map +1 -1
  95. package/lib/commonjs/features/browser.js +2 -2
  96. package/lib/commonjs/features/favorites.js +13 -10
  97. package/lib/commonjs/features/favorites.js.map +1 -1
  98. package/lib/commonjs/features/playback/controls.js +4 -2
  99. package/lib/commonjs/features/playback/controls.js.map +1 -1
  100. package/lib/commonjs/features/playback/state.js +2 -3
  101. package/lib/commonjs/features/playback/state.js.map +1 -1
  102. package/lib/commonjs/features/player/options.js.map +1 -1
  103. package/lib/commonjs/features/player/setup.js +3 -1
  104. package/lib/commonjs/features/player/setup.js.map +1 -1
  105. package/lib/commonjs/features/player/validateOptions.js +2 -0
  106. package/lib/commonjs/features/player/validateOptions.js.map +1 -1
  107. package/lib/commonjs/features/queue/activeTrack.js +14 -3
  108. package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
  109. package/lib/commonjs/features/queue/queue.js +11 -4
  110. package/lib/commonjs/features/queue/queue.js.map +1 -1
  111. package/lib/commonjs/utils/LazyNativeEmitter.js +21 -10
  112. package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
  113. package/lib/commonjs/utils/NativeUpdatedValue.js +31 -13
  114. package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
  115. package/lib/commonjs/utils/getTrackIdentity.js +24 -0
  116. package/lib/commonjs/utils/getTrackIdentity.js.map +1 -0
  117. package/lib/commonjs/utils/useNativeValueRefreshedBy.js +33 -0
  118. package/lib/commonjs/utils/useNativeValueRefreshedBy.js.map +1 -0
  119. package/lib/commonjs/web/NativeAudioBrowser.js +32 -24
  120. package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
  121. package/lib/commonjs/web/browser/BrowserManager.js +144 -87
  122. package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
  123. package/lib/commonjs/web/browser/FavoriteManager.js +30 -21
  124. package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
  125. package/lib/commonjs/web/browser/assertBrowsePageShape.js +2 -2
  126. package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -1
  127. package/lib/commonjs/web/browser/classifyTrackNavigation.js +9 -9
  128. package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -1
  129. package/lib/commonjs/web/browser/sections.js +89 -0
  130. package/lib/commonjs/web/browser/sections.js.map +1 -0
  131. package/lib/commonjs/web/player/NowPlayingManager.js +4 -3
  132. package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
  133. package/lib/commonjs/web/util/BrowserPathHelper.js +56 -11
  134. package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
  135. package/lib/module/AudioBrowser.js +1 -0
  136. package/lib/module/AudioBrowser.js.map +1 -1
  137. package/lib/module/features/browser-config.js +11 -8
  138. package/lib/module/features/browser-config.js.map +1 -1
  139. package/lib/module/features/browser.js +2 -2
  140. package/lib/module/features/favorites.js +13 -10
  141. package/lib/module/features/favorites.js.map +1 -1
  142. package/lib/module/features/playback/controls.js +4 -2
  143. package/lib/module/features/playback/controls.js.map +1 -1
  144. package/lib/module/features/playback/state.js +2 -3
  145. package/lib/module/features/playback/state.js.map +1 -1
  146. package/lib/module/features/player/options.js.map +1 -1
  147. package/lib/module/features/player/setup.js +3 -1
  148. package/lib/module/features/player/setup.js.map +1 -1
  149. package/lib/module/features/player/validateOptions.js +2 -0
  150. package/lib/module/features/player/validateOptions.js.map +1 -1
  151. package/lib/module/features/queue/activeTrack.js +14 -3
  152. package/lib/module/features/queue/activeTrack.js.map +1 -1
  153. package/lib/module/features/queue/queue.js +11 -4
  154. package/lib/module/features/queue/queue.js.map +1 -1
  155. package/lib/module/utils/LazyNativeEmitter.js +21 -10
  156. package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
  157. package/lib/module/utils/NativeUpdatedValue.js +31 -13
  158. package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
  159. package/lib/module/utils/getTrackIdentity.js +20 -0
  160. package/lib/module/utils/getTrackIdentity.js.map +1 -0
  161. package/lib/module/utils/useNativeValueRefreshedBy.js +29 -0
  162. package/lib/module/utils/useNativeValueRefreshedBy.js.map +1 -0
  163. package/lib/module/web/NativeAudioBrowser.js +32 -24
  164. package/lib/module/web/NativeAudioBrowser.js.map +1 -1
  165. package/lib/module/web/browser/BrowserManager.js +144 -87
  166. package/lib/module/web/browser/BrowserManager.js.map +1 -1
  167. package/lib/module/web/browser/FavoriteManager.js +31 -21
  168. package/lib/module/web/browser/FavoriteManager.js.map +1 -1
  169. package/lib/module/web/browser/assertBrowsePageShape.js +2 -2
  170. package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -1
  171. package/lib/module/web/browser/classifyTrackNavigation.js +9 -9
  172. package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -1
  173. package/lib/module/web/browser/sections.js +83 -0
  174. package/lib/module/web/browser/sections.js.map +1 -0
  175. package/lib/module/web/player/NowPlayingManager.js +4 -3
  176. package/lib/module/web/player/NowPlayingManager.js.map +1 -1
  177. package/lib/module/web/util/BrowserPathHelper.js +56 -11
  178. package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
  179. package/lib/typescript/src/AudioBrowser.d.ts +1 -0
  180. package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
  181. package/lib/typescript/src/features/browser-config.d.ts.map +1 -1
  182. package/lib/typescript/src/features/browser.d.ts +2 -2
  183. package/lib/typescript/src/features/favorites.d.ts +13 -10
  184. package/lib/typescript/src/features/favorites.d.ts.map +1 -1
  185. package/lib/typescript/src/features/playback/controls.d.ts +4 -2
  186. package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
  187. package/lib/typescript/src/features/playback/state.d.ts +2 -3
  188. package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
  189. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +1 -1
  190. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -1
  191. package/lib/typescript/src/features/player/options.d.ts +21 -19
  192. package/lib/typescript/src/features/player/options.d.ts.map +1 -1
  193. package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
  194. package/lib/typescript/src/features/player/validateOptions.d.ts +1 -8
  195. package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -1
  196. package/lib/typescript/src/features/queue/activeTrack.d.ts +8 -1
  197. package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
  198. package/lib/typescript/src/features/queue/queue.d.ts +5 -2
  199. package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
  200. package/lib/typescript/src/types/browser-native.d.ts +2 -2
  201. package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
  202. package/lib/typescript/src/types/browser-nodes.d.ts +260 -127
  203. package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
  204. package/lib/typescript/src/types/browser.d.ts +33 -53
  205. package/lib/typescript/src/types/browser.d.ts.map +1 -1
  206. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +16 -17
  207. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
  208. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +24 -19
  209. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
  210. package/lib/typescript/src/utils/getTrackIdentity.d.ts +18 -0
  211. package/lib/typescript/src/utils/getTrackIdentity.d.ts.map +1 -0
  212. package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts +21 -0
  213. package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts.map +1 -0
  214. package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +0 -1
  215. package/nitrogen/generated/android/c++/{JFavoritesMatchMode.hpp → JArtworkRendering.hpp} +15 -15
  216. package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +4 -3
  217. package/nitrogen/generated/android/c++/JBrowseResult.hpp +10 -4
  218. package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
  219. package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +4 -3
  220. package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +4 -3
  221. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +4 -3
  222. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +4 -3
  223. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +4 -3
  224. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +4 -3
  225. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +4 -2
  226. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +4 -2
  227. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +10 -4
  228. package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +4 -3
  229. package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +2 -5
  230. package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +4 -3
  231. package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +9 -3
  232. package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +4 -2
  233. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +21 -14
  234. package/nitrogen/generated/android/c++/JIOSOptions.hpp +9 -3
  235. package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +4 -3
  236. package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +16 -10
  237. package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +8 -2
  238. package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +6 -7
  239. package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +2 -5
  240. package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +9 -3
  241. package/nitrogen/generated/android/c++/JOptions.hpp +2 -5
  242. package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +4 -3
  243. package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
  244. package/nitrogen/generated/android/c++/JResolvedTrack.hpp +49 -51
  245. package/nitrogen/generated/android/c++/JSection.hpp +114 -0
  246. package/nitrogen/generated/android/c++/JSectionStyle.hpp +69 -0
  247. package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
  248. package/nitrogen/generated/android/c++/JTrack.hpp +19 -48
  249. package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +4 -2
  250. package/nitrogen/generated/android/c++/JTrackStyle.hpp +25 -18
  251. package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +10 -4
  252. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → ArtworkRendering.kt} +5 -5
  253. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayPlayingIndicatorLocation.kt +23 -0
  254. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +6 -3
  255. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +5 -5
  256. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +6 -3
  257. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
  258. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -18
  259. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Section.kt +50 -0
  260. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → SectionStyle.kt} +12 -6
  261. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StyleDisplay.kt +23 -0
  262. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +6 -15
  263. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +23 -5
  264. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +104 -82
  265. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +16 -10
  266. package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +16 -10
  267. package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
  268. package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
  269. package/nitrogen/generated/ios/swift/IOSOptions.swift +7 -2
  270. package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +5 -5
  271. package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +12 -1
  272. package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
  273. package/nitrogen/generated/ios/swift/ResolvedTrack.swift +54 -83
  274. package/nitrogen/generated/ios/swift/Section.swift +100 -0
  275. package/nitrogen/generated/ios/swift/SectionStyle.swift +64 -0
  276. package/nitrogen/generated/ios/swift/StyleDisplay.swift +40 -0
  277. package/nitrogen/generated/ios/swift/Track.swift +20 -73
  278. package/nitrogen/generated/ios/swift/TrackStyle.swift +28 -22
  279. package/nitrogen/generated/shared/c++/{FavoritesMatchMode.hpp → ArtworkRendering.hpp} +17 -17
  280. package/nitrogen/generated/shared/c++/CarPlayPlayingIndicatorLocation.hpp +76 -0
  281. package/nitrogen/generated/shared/c++/IOSOptions.hpp +9 -2
  282. package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +6 -6
  283. package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +9 -2
  284. package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
  285. package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +30 -38
  286. package/nitrogen/generated/shared/c++/Section.hpp +106 -0
  287. package/nitrogen/generated/shared/c++/SectionStyle.hpp +96 -0
  288. package/nitrogen/generated/shared/c++/StyleDisplay.hpp +76 -0
  289. package/nitrogen/generated/shared/c++/Track.hpp +15 -31
  290. package/nitrogen/generated/shared/c++/TrackStyle.hpp +50 -34
  291. package/package.json +3 -2
  292. package/src/AudioBrowser.ts +1 -0
  293. package/src/features/browser-config.test.ts +21 -6
  294. package/src/features/browser-config.ts +11 -8
  295. package/src/features/browser.test.ts +1 -1
  296. package/src/features/browser.ts +2 -2
  297. package/src/features/favorites.ts +13 -10
  298. package/src/features/hook-wiring.test.ts +63 -15
  299. package/src/features/playback/controls.ts +4 -2
  300. package/src/features/playback/state.ts +2 -3
  301. package/src/features/playback/trackPlaybackTime.ts +1 -1
  302. package/src/features/player/options.ts +27 -19
  303. package/src/features/player/setup.test.ts +12 -3
  304. package/src/features/player/setup.ts +3 -1
  305. package/src/features/player/validateOptions.ts +2 -0
  306. package/src/features/queue/activeTrack.ts +14 -3
  307. package/src/features/queue/queue.ts +11 -4
  308. package/src/nitro-stub-drift.test.ts +277 -0
  309. package/src/public-surface.test.ts +40 -0
  310. package/src/specs/audio-browser.nitro.ts +4 -3
  311. package/src/style-resolution.test.ts +139 -0
  312. package/src/types/browser-native.ts +2 -2
  313. package/src/types/browser-nodes.ts +268 -136
  314. package/src/types/browser.ts +34 -55
  315. package/src/utils/LazyNativeEmitter.ts +20 -10
  316. package/src/utils/NativeUpdatedValue.ts +29 -13
  317. package/src/utils/getTrackIdentity.ts +20 -0
  318. package/src/utils/useNativeValueRefreshedBy.ts +38 -0
  319. package/src/web/NativeAudioBrowser.ts +37 -22
  320. package/src/web/browser/BrowserManager.ts +176 -95
  321. package/src/web/browser/FavoriteManager.test.ts +84 -0
  322. package/src/web/browser/FavoriteManager.ts +28 -23
  323. package/src/web/browser/assertBrowsePageShape.test.ts +1 -1
  324. package/src/web/browser/assertBrowsePageShape.ts +5 -3
  325. package/src/web/browser/classifyTrackNavigation.test.ts +10 -8
  326. package/src/web/browser/classifyTrackNavigation.ts +9 -9
  327. package/src/web/browser/parseSearchResponse.test.ts +8 -2
  328. package/src/web/browser/sections.test.ts +91 -0
  329. package/src/web/browser/sections.ts +72 -0
  330. package/src/web/player/NowPlayingManager.ts +4 -3
  331. package/src/web/util/BrowserPathHelper.test.ts +52 -0
  332. package/src/web/util/BrowserPathHelper.ts +61 -13
  333. package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
  334. package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
  335. package/nitrogen/generated/android/c++/JImageRowItem.hpp +0 -105
  336. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
  337. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
  338. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +0 -68
  339. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
  340. package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
  341. package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
  342. package/nitrogen/generated/ios/swift/ImageRowItem.swift +0 -195
  343. package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
  344. package/nitrogen/generated/shared/c++/FavoriteConfig.hpp +0 -84
  345. package/nitrogen/generated/shared/c++/ImageRowItem.hpp +0 -129
@@ -10,7 +10,7 @@ struct SFSymbolTests {
10
10
  let action = await CarPlayArtworkResolver.resolve(
11
11
  artwork: "sf:heart.fill",
12
12
  artworkSourceUri: nil,
13
- artworkCarPlayTinted: nil,
13
+ stencil: false,
14
14
  targetWidth: 40, targetHeight: 40,
15
15
  displayScale: 2.0,
16
16
  urlResolver: nil,
@@ -22,7 +22,7 @@ struct SFSymbolTests {
22
22
  let action = await CarPlayArtworkResolver.resolve(
23
23
  artwork: "sf:play.circle?bg=#000&fg=#fff",
24
24
  artworkSourceUri: nil,
25
- artworkCarPlayTinted: nil,
25
+ stencil: false,
26
26
  targetWidth: 24, targetHeight: 24,
27
27
  displayScale: 3.0,
28
28
  urlResolver: nil,
@@ -35,7 +35,7 @@ struct SFSymbolTests {
35
35
  let action = await CarPlayArtworkResolver.resolve(
36
36
  artwork: "sf:music.note",
37
37
  artworkSourceUri: nil,
38
- artworkCarPlayTinted: nil,
38
+ stencil: false,
39
39
  targetWidth: 40, targetHeight: 40,
40
40
  displayScale: 2.0,
41
41
  urlResolver: { _, _ in
@@ -51,7 +51,7 @@ struct SFSymbolTests {
51
51
  let action = await CarPlayArtworkResolver.resolve(
52
52
  artwork: nil,
53
53
  artworkSourceUri: "sf:star.fill",
54
- artworkCarPlayTinted: nil,
54
+ stencil: false,
55
55
  targetWidth: 20, targetHeight: 20,
56
56
  displayScale: 2.0,
57
57
  urlResolver: nil,
@@ -68,7 +68,7 @@ struct NoArtworkTests {
68
68
  let action = await CarPlayArtworkResolver.resolve(
69
69
  artwork: nil,
70
70
  artworkSourceUri: nil,
71
- artworkCarPlayTinted: nil,
71
+ stencil: false,
72
72
  targetWidth: 40, targetHeight: 40,
73
73
  displayScale: 2.0,
74
74
  urlResolver: nil,
@@ -80,7 +80,7 @@ struct NoArtworkTests {
80
80
  let action = await CarPlayArtworkResolver.resolve(
81
81
  artwork: nil,
82
82
  artworkSourceUri: nil,
83
- artworkCarPlayTinted: nil,
83
+ stencil: false,
84
84
  targetWidth: 40, targetHeight: 40,
85
85
  displayScale: 2.0,
86
86
  urlResolver: { _, _ in nil },
@@ -97,7 +97,7 @@ struct URLResolutionTests {
97
97
  let action = await CarPlayArtworkResolver.resolve(
98
98
  artwork: "https://example.com/art.jpg",
99
99
  artworkSourceUri: nil,
100
- artworkCarPlayTinted: false,
100
+ stencil: false,
101
101
  targetWidth: 40, targetHeight: 40,
102
102
  displayScale: 2.0,
103
103
  urlResolver: { pixelWidth, pixelHeight in
@@ -121,7 +121,7 @@ struct URLResolutionTests {
121
121
  let action = await CarPlayArtworkResolver.resolve(
122
122
  artwork: "https://example.com/art.jpg",
123
123
  artworkSourceUri: nil,
124
- artworkCarPlayTinted: nil,
124
+ stencil: false,
125
125
  targetWidth: 24, targetHeight: 24,
126
126
  displayScale: 3.0,
127
127
  urlResolver: { _, _ in
@@ -140,7 +140,7 @@ struct URLResolutionTests {
140
140
  let action = await CarPlayArtworkResolver.resolve(
141
141
  artwork: "https://example.com/art.jpg",
142
142
  artworkSourceUri: nil,
143
- artworkCarPlayTinted: nil,
143
+ stencil: false,
144
144
  targetWidth: 40, targetHeight: 40,
145
145
  displayScale: 2.0,
146
146
  urlResolver: { _, _ in nil },
@@ -157,7 +157,7 @@ struct URLResolutionTests {
157
157
  let action = await CarPlayArtworkResolver.resolve(
158
158
  artwork: "https://example.com/fallback.jpg",
159
159
  artworkSourceUri: nil,
160
- artworkCarPlayTinted: nil,
160
+ stencil: false,
161
161
  targetWidth: 40, targetHeight: 40,
162
162
  displayScale: 2.0,
163
163
  urlResolver: { _, _ in
@@ -180,7 +180,7 @@ struct URLResolutionTests {
180
180
  _ = await CarPlayArtworkResolver.resolve(
181
181
  artwork: "https://example.com/art.jpg",
182
182
  artworkSourceUri: nil,
183
- artworkCarPlayTinted: nil,
183
+ stencil: false,
184
184
  targetWidth: 50, targetHeight: 30,
185
185
  displayScale: 2.5,
186
186
  urlResolver: { w, h in
@@ -202,7 +202,7 @@ struct DirectFallbackTests {
202
202
  let action = await CarPlayArtworkResolver.resolve(
203
203
  artwork: "https://example.com/artwork.png",
204
204
  artworkSourceUri: nil,
205
- artworkCarPlayTinted: nil,
205
+ stencil: false,
206
206
  targetWidth: 40, targetHeight: 40,
207
207
  displayScale: 2.0,
208
208
  urlResolver: nil,
@@ -219,7 +219,7 @@ struct DirectFallbackTests {
219
219
  let action = await CarPlayArtworkResolver.resolve(
220
220
  artwork: nil,
221
221
  artworkSourceUri: "https://example.com/source.jpg",
222
- artworkCarPlayTinted: nil,
222
+ stencil: false,
223
223
  targetWidth: 40, targetHeight: 40,
224
224
  displayScale: 2.0,
225
225
  urlResolver: nil,
@@ -236,7 +236,7 @@ struct DirectFallbackTests {
236
236
  let action = await CarPlayArtworkResolver.resolve(
237
237
  artwork: "https://example.com/artwork.jpg",
238
238
  artworkSourceUri: "https://example.com/source.jpg",
239
- artworkCarPlayTinted: nil,
239
+ stencil: false,
240
240
  targetWidth: 40, targetHeight: 40,
241
241
  displayScale: 2.0,
242
242
  urlResolver: nil,
@@ -253,7 +253,7 @@ struct DirectFallbackTests {
253
253
  let action = await CarPlayArtworkResolver.resolve(
254
254
  artwork: "",
255
255
  artworkSourceUri: nil,
256
- artworkCarPlayTinted: nil,
256
+ stencil: false,
257
257
  targetWidth: 40, targetHeight: 40,
258
258
  displayScale: 2.0,
259
259
  urlResolver: nil,
@@ -270,7 +270,7 @@ struct SVGDetectionTests {
270
270
  let action = await CarPlayArtworkResolver.resolve(
271
271
  artwork: "https://example.com/icon.svg",
272
272
  artworkSourceUri: nil,
273
- artworkCarPlayTinted: nil,
273
+ stencil: false,
274
274
  targetWidth: 40, targetHeight: 40,
275
275
  displayScale: 2.0,
276
276
  urlResolver: nil,
@@ -287,7 +287,7 @@ struct SVGDetectionTests {
287
287
  let action = await CarPlayArtworkResolver.resolve(
288
288
  artwork: "https://example.com/icon.SVG",
289
289
  artworkSourceUri: nil,
290
- artworkCarPlayTinted: nil,
290
+ stencil: false,
291
291
  targetWidth: 40, targetHeight: 40,
292
292
  displayScale: 2.0,
293
293
  urlResolver: nil,
@@ -304,7 +304,7 @@ struct SVGDetectionTests {
304
304
  let action = await CarPlayArtworkResolver.resolve(
305
305
  artwork: "https://example.com/art.jpg",
306
306
  artworkSourceUri: nil,
307
- artworkCarPlayTinted: nil,
307
+ stencil: false,
308
308
  targetWidth: 40, targetHeight: 40,
309
309
  displayScale: 2.0,
310
310
  urlResolver: { _, _ in
@@ -323,7 +323,7 @@ struct SVGDetectionTests {
323
323
  let action = await CarPlayArtworkResolver.resolve(
324
324
  artwork: "https://example.com/photo.jpg",
325
325
  artworkSourceUri: nil,
326
- artworkCarPlayTinted: nil,
326
+ stencil: false,
327
327
  targetWidth: 40, targetHeight: 40,
328
328
  displayScale: 2.0,
329
329
  urlResolver: nil,
@@ -341,11 +341,11 @@ struct SVGDetectionTests {
341
341
 
342
342
  @Suite("tinting")
343
343
  struct TintingTests {
344
- @Test func artworkCarPlayTintedTrue_setsShouldTint() async {
344
+ @Test func stencilTrue_setsShouldTint() async {
345
345
  let action = await CarPlayArtworkResolver.resolve(
346
346
  artwork: "https://example.com/icon.png",
347
347
  artworkSourceUri: nil,
348
- artworkCarPlayTinted: true,
348
+ stencil: true,
349
349
  targetWidth: 40, targetHeight: 40,
350
350
  displayScale: 2.0,
351
351
  urlResolver: nil,
@@ -358,28 +358,11 @@ struct TintingTests {
358
358
  ))
359
359
  }
360
360
 
361
- @Test func artworkCarPlayTintedFalse_doesNotTint() async {
361
+ @Test func stencilFalse_doesNotTint() async {
362
362
  let action = await CarPlayArtworkResolver.resolve(
363
363
  artwork: "https://example.com/photo.jpg",
364
364
  artworkSourceUri: nil,
365
- artworkCarPlayTinted: false,
366
- targetWidth: 40, targetHeight: 40,
367
- displayScale: 2.0,
368
- urlResolver: nil,
369
- )
370
- #expect(action == .fetch(
371
- uri: "https://example.com/photo.jpg",
372
- headers: nil,
373
- shouldTint: false,
374
- isSvg: false,
375
- ))
376
- }
377
-
378
- @Test func artworkCarPlayTintedNil_defaultsToFalse() async {
379
- let action = await CarPlayArtworkResolver.resolve(
380
- artwork: "https://example.com/photo.jpg",
381
- artworkSourceUri: nil,
382
- artworkCarPlayTinted: nil,
365
+ stencil: false,
383
366
  targetWidth: 40, targetHeight: 40,
384
367
  displayScale: 2.0,
385
368
  urlResolver: nil,
@@ -396,7 +379,7 @@ struct TintingTests {
396
379
  let action = await CarPlayArtworkResolver.resolve(
397
380
  artwork: "https://example.com/icon.png",
398
381
  artworkSourceUri: nil,
399
- artworkCarPlayTinted: true,
382
+ stencil: true,
400
383
  targetWidth: 40, targetHeight: 40,
401
384
  displayScale: 2.0,
402
385
  urlResolver: { _, _ in
@@ -40,7 +40,7 @@ struct JsonTrackArtworkTests {
40
40
  // The shape that shipped broken: a container decodes its children eagerly,
41
41
  // so one unparseable row rejected the whole page rather than losing an image.
42
42
  let json = """
43
- { "url": "/favorites", "title": "Favorites", "children": [
43
+ { "path": "/favorites", "title": "Favorites", "children": [
44
44
  { "title": "Playlist", "artwork": { "light": "https://e.example/l.png", "dark": "https://e.example/d.png" } }
45
45
  ] }
46
46
  """.data(using: .utf8)!
@@ -56,3 +56,77 @@ struct JsonTrackArtworkTests {
56
56
  }
57
57
  }
58
58
  }
59
+
60
+ // MARK: - Style blocks (ADR 0011)
61
+
62
+ @Suite("JsonTrack.style")
63
+ struct JsonStyleTests {
64
+ @Test func decodesABlockOntoTrackStyle() throws {
65
+ let json = Data(
66
+ """
67
+ {"title":"X","style":{"display":"grid","artworkRendering":"stencil"}}
68
+ """.utf8)
69
+ let track = try JSONDecoder().decode(JsonTrack.self, from: json).toNitro()
70
+ #expect(track.style?.display == .grid)
71
+ #expect(track.style?.artworkRendering == .stencil)
72
+ }
73
+
74
+ @Test func decodesABlockOntoSectionStyle() throws {
75
+ let json = Data(
76
+ """
77
+ {"path":"/home","title":"Home","sections":[
78
+ {"style":{"display":"grid","gridWrap":false},"children":[{"title":"C","src":"s"}]}
79
+ ]}
80
+ """.utf8)
81
+ let resolved = try JSONDecoder().decode(JsonResolvedTrack.self, from: json).toNitro()
82
+ let style = resolved.sections?.first?.style
83
+ #expect(style?.display == .grid)
84
+ #expect(style?.gridWrap == false)
85
+ }
86
+
87
+ @Test func staleStringStyleDecodesAsNoDeclaration() throws {
88
+ // The retired string vocabulary must never kill the page (tolerant
89
+ // decoding, ADR 0011) — it decodes as an empty block.
90
+ let json = Data(
91
+ """
92
+ {"path":"/home","title":"Home","style":"rail","children":[
93
+ {"title":"C","src":"s","style":"grid"}
94
+ ]}
95
+ """.utf8)
96
+ let resolved = try JSONDecoder().decode(JsonResolvedTrack.self, from: json).toNitro()
97
+ #expect(resolved.style == nil)
98
+ #expect(resolved.children?.first?.style == nil)
99
+ }
100
+
101
+ @Test func unknownEnumValuesDecodeAsNoDeclaration() throws {
102
+ let json = Data(
103
+ """
104
+ {"title":"X","style":{"display":"carousel","artworkRendering":"embossed","gridWrap":"yes"}}
105
+ """.utf8)
106
+ let track = try JSONDecoder().decode(JsonTrack.self, from: json).toNitro()
107
+ // All-unknown declarations collapse to "no block" — one shape for "no
108
+ // declaration" on every platform.
109
+ #expect(track.style == nil)
110
+ }
111
+
112
+ @Test func decodesDisabled() throws {
113
+ let json = Data("""
114
+ {"title":"X","src":"s","disabled":true}
115
+ """.utf8)
116
+ let track = try JSONDecoder().decode(JsonTrack.self, from: json).toNitro()
117
+ #expect(track.disabled == true)
118
+ }
119
+
120
+ @Test func styleAndDisabledSurviveThePersistenceRoundTrip() throws {
121
+ // Restored queues keep their visible fidelity across process death.
122
+ let original = JsonTrack(
123
+ title: "X", src: "s",
124
+ style: JsonStyle(display: "grid", artworkRendering: "stencil"),
125
+ disabled: true,
126
+ )
127
+ let encoded = try JSONEncoder().encode(original)
128
+ let decoded = try JSONDecoder().decode(JsonTrack.self, from: encoded)
129
+ #expect(decoded.style == original.style)
130
+ #expect(decoded.disabled == true)
131
+ }
132
+ }
@@ -14,10 +14,11 @@ struct PlaybackStateStoreTests {
14
14
 
15
15
  private func sampleTrack() -> JsonTrack {
16
16
  JsonTrack(
17
- id: "abc", url: nil, title: "Test FM", subtitle: nil, artwork: nil,
18
- artist: "City, Country", albumUrl: nil, album: nil, description: nil,
17
+ id: "abc", path: nil, title: "Test FM", subtitle: nil, artwork: nil,
18
+ artist: "City, Country", albumPath: nil, album: nil, description: nil,
19
19
  genre: nil, duration: nil, src: "/listen/abc/channel.mp3", request: nil,
20
- style: nil, childrenStyle: nil, groupTitle: nil, live: true, imageRow: nil,
20
+ style: JsonStyle(display: "grid", artworkRendering: "stencil"),
21
+ disabled: false, live: true,
21
22
  )
22
23
  }
23
24
 
@@ -813,13 +813,13 @@ struct ComputedPropertyTests {
813
813
  let q = QueueManager()
814
814
  q.setQueue(tracks("a", "b", "c", "d"))
815
815
  q.shuffleEnabled = true
816
- let nextFromProp = q.nextTracks.map(\.id)
816
+ let nextFromProp = q.nextTracks.map { $0.id! }
817
817
  // Navigate forward only as many times as nextTracks reports
818
818
  // (nextTracks does not include wrapped tracks)
819
819
  var nextFromNav: [String] = []
820
820
  for _ in 0 ..< nextFromProp.count {
821
821
  _ = q.next()
822
- nextFromNav.append(q.currentTrack!.id)
822
+ nextFromNav.append(q.currentTrack!.id!)
823
823
  }
824
824
  #expect(nextFromProp == nextFromNav)
825
825
  }
@@ -832,12 +832,12 @@ struct ComputedPropertyTests {
832
832
  for _ in 0 ..< 3 {
833
833
  _ = q.next()
834
834
  }
835
- let prevFromProp = q.previousTracks.map(\.id)
835
+ let prevFromProp = q.previousTracks.map { $0.id! }
836
836
  // Navigate backward the same number of times
837
837
  var prevFromNav: [String] = []
838
838
  for _ in 0 ..< prevFromProp.count {
839
839
  _ = q.previous()
840
- prevFromNav.insert(q.currentTrack!.id, at: 0)
840
+ prevFromNav.insert(q.currentTrack!.id!, at: 0)
841
841
  }
842
842
  #expect(prevFromProp == prevFromNav)
843
843
  }
@@ -3,17 +3,18 @@ import Testing
3
3
  @testable import AudioBrowserTestable
4
4
 
5
5
  private func makeResolvedTrack(
6
- url: String = "/test",
6
+ path: String = "/test",
7
7
  title: String = "Test Track",
8
8
  ) -> ResolvedTrack {
9
9
  ResolvedTrack(
10
- url: url,
10
+ path: path,
11
+ style: nil,
12
+ sections: nil,
11
13
  children: nil,
12
14
  carPlaySiriListButton: nil,
13
15
  src: nil,
14
16
  artwork: nil,
15
17
  artworkSource: nil,
16
- artworkCarPlayTinted: nil,
17
18
  title: title,
18
19
  subtitle: nil,
19
20
  artist: nil,
@@ -21,25 +22,23 @@ private func makeResolvedTrack(
21
22
  description: nil,
22
23
  genre: nil,
23
24
  duration: nil,
24
- style: nil,
25
- childrenStyle: nil,
25
+ disabled: nil,
26
26
  favorited: nil,
27
- groupTitle: nil,
28
27
  live: nil,
29
- imageRow: nil,
30
28
  )
31
29
  }
32
30
 
33
31
  private func makeFullResolvedTrack() -> ResolvedTrack {
34
32
  ResolvedTrack(
35
- url: "/original",
36
- children: [Track(id: "t1", url: "/t1")],
33
+ path: "/original",
34
+ style: SectionStyle(gridWrap: false, display: .grid, artworkRendering: .stencil),
35
+ sections: [Section(title: "Group A", subtitle: nil, style: nil, path: nil, children: [])],
36
+ children: [Track(id: "t1", path: "/t1")],
37
37
  carPlaySiriListButton: .top,
38
38
  id: "original-id",
39
39
  src: "src.mp3",
40
40
  artwork: .first("art.jpg"),
41
41
  artworkSource: ImageSource(uri: "resolved-art.jpg"),
42
- artworkCarPlayTinted: true,
43
42
  title: "Original Title",
44
43
  subtitle: "Original Subtitle",
45
44
  artist: "Original Artist",
@@ -47,12 +46,9 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
47
46
  description: "Original Description",
48
47
  genre: "Rock",
49
48
  duration: 180.0,
50
- style: .list,
51
- childrenStyle: .grid,
49
+ disabled: false,
52
50
  favorited: true,
53
- groupTitle: "Group A",
54
51
  live: false,
55
- imageRow: [ImageRowItem(title: "Row 1")],
56
52
  )
57
53
  }
58
54
 
@@ -64,12 +60,12 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
64
60
  #expect(copy == original)
65
61
  }
66
62
 
67
- // MARK: - non-optional fields (url, title)
63
+ // MARK: - non-optional fields (path, title)
68
64
 
69
- @Test func copyingOverridesUrl() {
70
- let original = makeResolvedTrack(url: "/old")
71
- let copy = original.copying(url: "/new")
72
- #expect(copy.url == "/new")
65
+ @Test func copyingOverridesPath() {
66
+ let original = makeResolvedTrack(path: "/old")
67
+ let copy = original.copying(path: "/new")
68
+ #expect(copy.path == "/new")
73
69
  #expect(copy.title == "Test Track")
74
70
  }
75
71
 
@@ -77,14 +73,14 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
77
73
  let original = makeResolvedTrack(title: "Old")
78
74
  let copy = original.copying(title: "New")
79
75
  #expect(copy.title == "New")
80
- #expect(copy.url == "/test")
76
+ #expect(copy.path == "/test")
81
77
  }
82
78
 
83
79
  // MARK: - optional fields: set value
84
80
 
85
81
  @Test func copyingOverridesChildren() {
86
82
  let original = makeResolvedTrack()
87
- let children = [Track(id: "c1", url: "/c1"), Track(id: "c2", url: "/c2")]
83
+ let children = [Track(id: "c1", path: "/c1"), Track(id: "c2", path: "/c2")]
88
84
  let copy = original.copying(children: children)
89
85
  #expect(copy.children?.count == 2)
90
86
  #expect(copy.children?[0].id == "c1")
@@ -135,8 +131,14 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
135
131
 
136
132
  @Test func copyingOverridesStyle() {
137
133
  let original = makeResolvedTrack()
138
- let copy = original.copying(style: .grid)
139
- #expect(copy.style == .grid)
134
+ let copy = original.copying(style: SectionStyle(gridWrap: nil, display: .grid, artworkRendering: nil))
135
+ #expect(copy.style?.display == .grid)
136
+ }
137
+
138
+ @Test func copyingOverridesDisabled() {
139
+ let original = makeResolvedTrack()
140
+ let copy = original.copying(disabled: true)
141
+ #expect(copy.disabled == true)
140
142
  }
141
143
 
142
144
  @Test func copyingOverridesFavorited() {
@@ -157,11 +159,13 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
157
159
  #expect(copy.carPlaySiriListButton == .bottom)
158
160
  }
159
161
 
160
- @Test func copyingOverridesImageRow() {
162
+ @Test func copyingOverridesSections() {
161
163
  let original = makeResolvedTrack()
162
- let items = [ImageRowItem(title: "Item 1"), ImageRowItem(title: "Item 2")]
163
- let copy = original.copying(imageRow: items)
164
- #expect(copy.imageRow == items)
164
+ let sections = [
165
+ Section(title: "A", subtitle: nil, style: nil, path: nil, children: [Track(id: "c1", src: "c1")]),
166
+ ]
167
+ let copy = original.copying(sections: sections)
168
+ #expect(copy.sections == sections)
165
169
  }
166
170
 
167
171
  // MARK: - optional fields: set to nil via .some(nil)
@@ -206,13 +210,13 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
206
210
  @Test func copyingOverridesMultipleFields() {
207
211
  let original = makeResolvedTrack()
208
212
  let copy = original.copying(
209
- url: "/updated",
213
+ path: "/updated",
210
214
  title: "Updated Title",
211
215
  artist: "New Artist",
212
216
  duration: 240.0,
213
217
  favorited: true,
214
218
  )
215
- #expect(copy.url == "/updated")
219
+ #expect(copy.path == "/updated")
216
220
  #expect(copy.title == "Updated Title")
217
221
  #expect(copy.artist == "New Artist")
218
222
  #expect(copy.duration == 240.0)
@@ -230,23 +234,21 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
230
234
  let copy = original.copying(title: "Changed Only Title")
231
235
  #expect(copy.title == "Changed Only Title")
232
236
  // All other fields unchanged
233
- #expect(copy.url == "/original")
237
+ #expect(copy.path == "/original")
234
238
  #expect(copy.children?.count == 1)
235
239
  #expect(copy.carPlaySiriListButton == .top)
236
240
  #expect(copy.src == "src.mp3")
237
241
  #expect(copy.artwork?.url == "art.jpg")
238
242
  #expect(copy.artworkSource == ImageSource(uri: "resolved-art.jpg"))
239
- #expect(copy.artworkCarPlayTinted == true)
240
243
  #expect(copy.subtitle == "Original Subtitle")
241
244
  #expect(copy.artist == "Original Artist")
242
245
  #expect(copy.album == "Original Album")
243
246
  #expect(copy.description == "Original Description")
244
247
  #expect(copy.genre == "Rock")
245
248
  #expect(copy.duration == 180.0)
246
- #expect(copy.style == .list)
247
- #expect(copy.childrenStyle == .grid)
249
+ #expect(copy.style == SectionStyle(gridWrap: false, display: .grid, artworkRendering: .stencil))
250
+ #expect(copy.disabled == false)
248
251
  #expect(copy.favorited == true)
249
- #expect(copy.groupTitle == "Group A")
252
+ #expect(copy.sections?.first?.title == "Group A")
250
253
  #expect(copy.live == false)
251
- #expect(copy.imageRow == [ImageRowItem(title: "Row 1")])
252
254
  }
@@ -4,14 +4,14 @@ import Testing
4
4
 
5
5
  private struct Item: PlayableSearchItem {
6
6
  let src: String?
7
- let url: String?
7
+ let path: String?
8
8
  }
9
9
 
10
10
  @Suite("SearchDrillIn.playable")
11
11
  struct SearchDrillInTests {
12
12
  /// A resolver that fails the test if the drill-in path calls it.
13
- private func unusedResolver(_ url: String) async throws -> [Item] {
14
- Issue.record("resolveChildren should not have been called (url: \(url))")
13
+ private func unusedResolver(_ path: String) async throws -> [Item] {
14
+ Issue.record("resolveChildren should not have been called (path: \(path))")
15
15
  return []
16
16
  }
17
17
 
@@ -22,9 +22,9 @@ struct SearchDrillInTests {
22
22
 
23
23
  @Test func firstPlayable_returnsFlatPlayable_inOrder_withoutResolving() async throws {
24
24
  let items = [
25
- Item(src: "/a.mp3", url: nil),
26
- Item(src: nil, url: "/page/x"), // browsable, filtered out
27
- Item(src: "/b.mp3", url: nil),
25
+ Item(src: "/a.mp3", path: nil),
26
+ Item(src: nil, path: "/page/x"), // browsable, filtered out
27
+ Item(src: "/b.mp3", path: nil),
28
28
  ]
29
29
  let result = try await SearchDrillIn.playable(from: items, resolveChildren: unusedResolver)
30
30
  #expect(result?.compactMap(\.src) == ["/a.mp3", "/b.mp3"])
@@ -32,31 +32,31 @@ struct SearchDrillInTests {
32
32
 
33
33
  @Test func firstBrowsable_drillsIntoIt_andPlaysItsChildren() async throws {
34
34
  let items = [
35
- Item(src: nil, url: "/page/place"), // browsable first result
36
- Item(src: "/later.mp3", url: nil), // would win without drill-in
35
+ Item(src: nil, path: "/page/place"), // browsable first result
36
+ Item(src: "/later.mp3", path: nil), // would win without drill-in
37
37
  ]
38
- let result = try await SearchDrillIn.playable(from: items) { url in
39
- #expect(url == "/page/place")
40
- return [Item(src: "/station1.mp3", url: nil), Item(src: "/station2.mp3", url: nil)]
38
+ let result = try await SearchDrillIn.playable(from: items) { path in
39
+ #expect(path == "/page/place")
40
+ return [Item(src: "/station1.mp3", path: nil), Item(src: "/station2.mp3", path: nil)]
41
41
  }
42
42
  #expect(result?.compactMap(\.src) == ["/station1.mp3", "/station2.mp3"])
43
43
  }
44
44
 
45
45
  @Test func browsableDrillYieldsNothing_fallsBackToFlat() async throws {
46
46
  let items = [
47
- Item(src: nil, url: "/page/empty"),
48
- Item(src: "/fallback.mp3", url: nil),
47
+ Item(src: nil, path: "/page/empty"),
48
+ Item(src: "/fallback.mp3", path: nil),
49
49
  ]
50
50
  let result = try await SearchDrillIn.playable(from: items) { _ in
51
- [Item(src: nil, url: "/page/nested")] // nothing playable inside
51
+ [Item(src: nil, path: "/page/nested")] // nothing playable inside
52
52
  }
53
53
  #expect(result?.compactMap(\.src) == ["/fallback.mp3"])
54
54
  }
55
55
 
56
56
  @Test func nothingPlayableAnywhere_returnsNil() async throws {
57
- let items = [Item(src: nil, url: "/page/a"), Item(src: nil, url: "/page/b")]
57
+ let items = [Item(src: nil, path: "/page/a"), Item(src: nil, path: "/page/b")]
58
58
  let result = try await SearchDrillIn.playable(from: items) { _ in
59
- [Item(src: nil, url: "/page/nested")]
59
+ [Item(src: nil, path: "/page/nested")]
60
60
  }
61
61
  #expect(result == nil)
62
62
  }
@@ -0,0 +1,61 @@
1
+ import Testing
2
+
3
+ @testable import AudioBrowserTestable
4
+
5
+ // The (resolved style, platform capability) → rendered-form mapping (ADR
6
+ // 0011): degradation drops decorations before layout, and the one layout drop
7
+ // is a wrapping grid on a platform without a wrapping tile container.
8
+
9
+ @Suite("SectionPresentation")
10
+ struct SectionPresentationTests {
11
+ private func style(display: StyleDisplay?, gridWrap: Bool? = nil) -> SectionStyle {
12
+ SectionStyle(gridWrap: gridWrap, display: display, artworkRendering: nil)
13
+ }
14
+
15
+ @Test func noStyleRendersAList() {
16
+ #expect(SectionPresentation(for: nil, supportsWrappingGrid: true) == .list)
17
+ #expect(SectionPresentation(for: nil, supportsWrappingGrid: false) == .list)
18
+ }
19
+
20
+ @Test func listDisplayRendersAList() {
21
+ #expect(SectionPresentation(for: style(display: .list), supportsWrappingGrid: true) == .list)
22
+ }
23
+
24
+ @Test func wrappingGridNeedsTheWrappingContainer() {
25
+ #expect(
26
+ SectionPresentation(for: style(display: .grid), supportsWrappingGrid: true) == .wrappingGrid)
27
+ // The one layout drop: a wrapping grid has nowhere to go but a list —
28
+ // every item stays reachable instead of truncating at an unknowable width.
29
+ #expect(SectionPresentation(for: style(display: .grid), supportsWrappingGrid: false) == .list)
30
+ }
31
+
32
+ @Test func singleLineGridRendersOnEveryOS() {
33
+ // gridWrap: false is the legacy image row's own shape, so it never
34
+ // degrades — declaring it INCREASES pre-26 fidelity.
35
+ #expect(
36
+ SectionPresentation(for: style(display: .grid, gridWrap: false), supportsWrappingGrid: true)
37
+ == .singleLineRow)
38
+ #expect(
39
+ SectionPresentation(for: style(display: .grid, gridWrap: false), supportsWrappingGrid: false)
40
+ == .singleLineRow)
41
+ }
42
+
43
+ @Test func explicitWrapTrueBehavesLikeTheDefault() {
44
+ #expect(
45
+ SectionPresentation(for: style(display: .grid, gridWrap: true), supportsWrappingGrid: true)
46
+ == .wrappingGrid)
47
+ #expect(
48
+ SectionPresentation(for: style(display: .grid, gridWrap: true), supportsWrappingGrid: false)
49
+ == .list)
50
+ }
51
+
52
+ @Test func gridWrapIsInertOnLists() {
53
+ // Declaring a decoration never changes the layout you'd get without it.
54
+ #expect(
55
+ SectionPresentation(for: style(display: .list, gridWrap: false), supportsWrappingGrid: true)
56
+ == .list)
57
+ #expect(
58
+ SectionPresentation(for: style(display: nil, gridWrap: false), supportsWrappingGrid: true)
59
+ == .list)
60
+ }
61
+ }