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

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 (416) hide show
  1. package/README.md +14 -4
  2. package/android/build.gradle +20 -1
  3. package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +115 -26
  4. package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +262 -188
  5. package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +23 -3
  6. package/android/src/main/java/com/audiobrowser/browser/InertStyleDiagnostic.kt +267 -0
  7. package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +95 -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 +60 -0
  10. package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +19 -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/model/PlayerUpdateOptions.kt +1 -1
  14. package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +120 -67
  15. package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +21 -24
  16. package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +109 -74
  17. package/android/src/main/java/com/audiobrowser/player/Player.kt +51 -30
  18. package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
  19. package/android/src/main/java/com/audiobrowser/player/ShuffleOrderLedBy.kt +30 -0
  20. package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +44 -40
  21. package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +28 -23
  22. package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +108 -0
  23. package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +52 -53
  24. package/android/src/test/java/com/audiobrowser/TestFixtures.kt +74 -41
  25. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
  26. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +229 -50
  27. package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +38 -0
  28. package/android/src/test/java/com/audiobrowser/browser/InertStyleDiagnosticTest.kt +277 -0
  29. package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +1 -1
  30. package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +147 -0
  31. package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -53
  32. package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +154 -0
  33. package/android/src/test/java/com/audiobrowser/extension/TappedTrackTest.kt +43 -0
  34. package/android/src/test/java/com/audiobrowser/extension/TrackIdentityTest.kt +41 -0
  35. package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +1 -2
  36. package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +42 -2
  37. package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +55 -0
  38. package/android/src/test/java/com/audiobrowser/player/ShuffleOrderLedByTest.kt +79 -0
  39. package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +41 -16
  40. package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +161 -0
  41. package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +7 -7
  42. package/ios/Browser/BrowserConfig.swift +102 -5
  43. package/ios/Browser/BrowserManager+URLResolution.swift +18 -14
  44. package/ios/Browser/BrowserManager.swift +229 -180
  45. package/ios/Browser/BrowserPathHelper.swift +44 -44
  46. package/ios/Browser/InertStyleDiagnostic.swift +279 -0
  47. package/ios/Browser/JsonModels.swift +258 -106
  48. package/ios/Browser/SearchDrillIn.swift +6 -6
  49. package/ios/Browser/SectionPresentation.swift +64 -0
  50. package/ios/Browser/SectionScope.swift +58 -24
  51. package/ios/Browser/StyleResolver.swift +58 -0
  52. package/ios/Browser/TabBarEntries.swift +9 -3
  53. package/ios/Browser/TrackIdTemplate.swift +37 -0
  54. package/ios/CarPlay/CarPlayArtworkResolver.swift +8 -7
  55. package/ios/CarPlay/CarPlayController.swift +227 -85
  56. package/ios/CarPlay/CarPlayImageLoader.swift +38 -18
  57. package/ios/CarPlay/CarPlayListItemFactory.swift +289 -84
  58. package/ios/CarPlay/CarPlayNowPlayingManager.swift +15 -12
  59. package/ios/Extension/Capability+RemoteCommand.swift +1 -1
  60. package/ios/Extension/PlayerCapabilities+Favorite.swift +1 -15
  61. package/ios/Extension/ResolvedTrack+Copying.swift +11 -15
  62. package/ios/Extension/Section+Copying.swift +31 -0
  63. package/ios/Extension/Track+Copying.swift +6 -12
  64. package/ios/Extension/Track+Identity.swift +18 -0
  65. package/ios/Extension/UIApplication+ActiveScene.swift +27 -0
  66. package/ios/Http/HttpClient.swift +8 -2
  67. package/ios/HybridAudioBrowser.swift +166 -45
  68. package/ios/Model/NitroTypeStubs.swift +106 -21
  69. package/ios/Model/PlayerUpdateOptions.swift +7 -0
  70. package/ios/Player/MediaIntentCriteria.swift +13 -5
  71. package/ios/Player/MediaLoader.swift +33 -9
  72. package/ios/Player/NowPlayingUpdater.swift +54 -6
  73. package/ios/Player/PlaybackCoordinator.swift +13 -6
  74. package/ios/Player/PlaybackTimer.swift +8 -2
  75. package/ios/Player/QueueManager.swift +36 -10
  76. package/ios/Player/RetryManager.swift +4 -3
  77. package/ios/Player/ShuffleOrder.swift +23 -0
  78. package/ios/Player/SleepTimerHandling.swift +13 -0
  79. package/ios/RemoteCommand/RemoteCommandController.swift +53 -18
  80. package/ios/Tests/BrowserPathHelperTests.swift +50 -20
  81. package/ios/Tests/CarPlayArtworkResolverTests.swift +24 -41
  82. package/ios/Tests/InertStyleDiagnosticTests.swift +200 -0
  83. package/ios/Tests/JsonModelsTests.swift +150 -1
  84. package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
  85. package/ios/Tests/QueueManagerTests.swift +72 -6
  86. package/ios/Tests/RemoteCommandControllerTests.swift +16 -0
  87. package/ios/Tests/ResolvedTrackCopyingTests.swift +37 -35
  88. package/ios/Tests/SearchDrillInTests.swift +16 -16
  89. package/ios/Tests/SectionPresentationTests.swift +90 -0
  90. package/ios/Tests/SectionScopeTests.swift +87 -55
  91. package/ios/Tests/StyleResolverTests.swift +127 -0
  92. package/ios/Tests/Support/TestTypes.swift +7 -0
  93. package/ios/Tests/TabBarEntriesTests.swift +18 -18
  94. package/ios/Tests/TrackIdTemplateTests.swift +67 -0
  95. package/ios/Tests/TrackIdentityTests.swift +20 -0
  96. package/ios/Tests/TrackSelectorTests.swift +168 -33
  97. package/ios/TrackPlayer.swift +57 -34
  98. package/ios/TrackSelector.swift +45 -30
  99. package/ios/Util/ArtworkImageFetcher.swift +16 -1
  100. package/ios/Util/NetworkMonitor.swift +43 -26
  101. package/ios/Util/SFSymbolRenderer.swift +24 -2
  102. package/ios/Util/SVGProcessor.swift +9 -2
  103. package/lib/commonjs/AudioBrowser.js +11 -0
  104. package/lib/commonjs/AudioBrowser.js.map +1 -1
  105. package/lib/commonjs/features/browser-config.js +11 -8
  106. package/lib/commonjs/features/browser-config.js.map +1 -1
  107. package/lib/commonjs/features/browser.js +2 -2
  108. package/lib/commonjs/features/favorites.js +13 -10
  109. package/lib/commonjs/features/favorites.js.map +1 -1
  110. package/lib/commonjs/features/playback/controls.js +4 -2
  111. package/lib/commonjs/features/playback/controls.js.map +1 -1
  112. package/lib/commonjs/features/playback/progress.js +2 -1
  113. package/lib/commonjs/features/playback/progress.js.map +1 -1
  114. package/lib/commonjs/features/playback/rate.js +8 -1
  115. package/lib/commonjs/features/playback/rate.js.map +1 -1
  116. package/lib/commonjs/features/playback/state.js +2 -3
  117. package/lib/commonjs/features/playback/state.js.map +1 -1
  118. package/lib/commonjs/features/player/options.js.map +1 -1
  119. package/lib/commonjs/features/player/setup.js +3 -1
  120. package/lib/commonjs/features/player/setup.js.map +1 -1
  121. package/lib/commonjs/features/player/validateOptions.js +2 -0
  122. package/lib/commonjs/features/player/validateOptions.js.map +1 -1
  123. package/lib/commonjs/features/queue/activeTrack.js +14 -3
  124. package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
  125. package/lib/commonjs/features/queue/queue.js +11 -4
  126. package/lib/commonjs/features/queue/queue.js.map +1 -1
  127. package/lib/commonjs/utils/LazyNativeEmitter.js +21 -10
  128. package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
  129. package/lib/commonjs/utils/NativeUpdatedValue.js +31 -13
  130. package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
  131. package/lib/commonjs/utils/getTrackIdentity.js +24 -0
  132. package/lib/commonjs/utils/getTrackIdentity.js.map +1 -0
  133. package/lib/commonjs/utils/useNativeValueRefreshedBy.js +33 -0
  134. package/lib/commonjs/utils/useNativeValueRefreshedBy.js.map +1 -0
  135. package/lib/commonjs/web/NativeAudioBrowser.js +32 -24
  136. package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
  137. package/lib/commonjs/web/TrackPlayer/QueueManager.js +14 -0
  138. package/lib/commonjs/web/TrackPlayer/QueueManager.js.map +1 -1
  139. package/lib/commonjs/web/browser/BrowserManager.js +144 -87
  140. package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
  141. package/lib/commonjs/web/browser/FavoriteManager.js +30 -21
  142. package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
  143. package/lib/commonjs/web/browser/assertBrowsePageShape.js +2 -2
  144. package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -1
  145. package/lib/commonjs/web/browser/classifyTrackNavigation.js +9 -9
  146. package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -1
  147. package/lib/commonjs/web/browser/sections.js +89 -0
  148. package/lib/commonjs/web/browser/sections.js.map +1 -0
  149. package/lib/commonjs/web/player/NowPlayingManager.js +4 -3
  150. package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
  151. package/lib/commonjs/web/util/BrowserPathHelper.js +56 -11
  152. package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
  153. package/lib/module/AudioBrowser.js +1 -0
  154. package/lib/module/AudioBrowser.js.map +1 -1
  155. package/lib/module/features/browser-config.js +11 -8
  156. package/lib/module/features/browser-config.js.map +1 -1
  157. package/lib/module/features/browser.js +2 -2
  158. package/lib/module/features/favorites.js +13 -10
  159. package/lib/module/features/favorites.js.map +1 -1
  160. package/lib/module/features/playback/controls.js +4 -2
  161. package/lib/module/features/playback/controls.js.map +1 -1
  162. package/lib/module/features/playback/progress.js +2 -1
  163. package/lib/module/features/playback/progress.js.map +1 -1
  164. package/lib/module/features/playback/rate.js +8 -1
  165. package/lib/module/features/playback/rate.js.map +1 -1
  166. package/lib/module/features/playback/state.js +2 -3
  167. package/lib/module/features/playback/state.js.map +1 -1
  168. package/lib/module/features/player/options.js.map +1 -1
  169. package/lib/module/features/player/setup.js +3 -1
  170. package/lib/module/features/player/setup.js.map +1 -1
  171. package/lib/module/features/player/validateOptions.js +2 -0
  172. package/lib/module/features/player/validateOptions.js.map +1 -1
  173. package/lib/module/features/queue/activeTrack.js +14 -3
  174. package/lib/module/features/queue/activeTrack.js.map +1 -1
  175. package/lib/module/features/queue/queue.js +11 -4
  176. package/lib/module/features/queue/queue.js.map +1 -1
  177. package/lib/module/utils/LazyNativeEmitter.js +21 -10
  178. package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
  179. package/lib/module/utils/NativeUpdatedValue.js +31 -13
  180. package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
  181. package/lib/module/utils/getTrackIdentity.js +20 -0
  182. package/lib/module/utils/getTrackIdentity.js.map +1 -0
  183. package/lib/module/utils/useNativeValueRefreshedBy.js +29 -0
  184. package/lib/module/utils/useNativeValueRefreshedBy.js.map +1 -0
  185. package/lib/module/web/NativeAudioBrowser.js +32 -24
  186. package/lib/module/web/NativeAudioBrowser.js.map +1 -1
  187. package/lib/module/web/TrackPlayer/QueueManager.js +14 -0
  188. package/lib/module/web/TrackPlayer/QueueManager.js.map +1 -1
  189. package/lib/module/web/browser/BrowserManager.js +144 -87
  190. package/lib/module/web/browser/BrowserManager.js.map +1 -1
  191. package/lib/module/web/browser/FavoriteManager.js +31 -21
  192. package/lib/module/web/browser/FavoriteManager.js.map +1 -1
  193. package/lib/module/web/browser/assertBrowsePageShape.js +2 -2
  194. package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -1
  195. package/lib/module/web/browser/classifyTrackNavigation.js +9 -9
  196. package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -1
  197. package/lib/module/web/browser/sections.js +83 -0
  198. package/lib/module/web/browser/sections.js.map +1 -0
  199. package/lib/module/web/player/NowPlayingManager.js +4 -3
  200. package/lib/module/web/player/NowPlayingManager.js.map +1 -1
  201. package/lib/module/web/util/BrowserPathHelper.js +56 -11
  202. package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
  203. package/lib/typescript/src/AudioBrowser.d.ts +1 -0
  204. package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
  205. package/lib/typescript/src/features/browser-config.d.ts.map +1 -1
  206. package/lib/typescript/src/features/browser.d.ts +2 -2
  207. package/lib/typescript/src/features/favorites.d.ts +13 -10
  208. package/lib/typescript/src/features/favorites.d.ts.map +1 -1
  209. package/lib/typescript/src/features/playback/controls.d.ts +4 -2
  210. package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
  211. package/lib/typescript/src/features/playback/progress.d.ts.map +1 -1
  212. package/lib/typescript/src/features/playback/rate.d.ts +8 -1
  213. package/lib/typescript/src/features/playback/rate.d.ts.map +1 -1
  214. package/lib/typescript/src/features/playback/state.d.ts +2 -3
  215. package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
  216. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +1 -1
  217. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -1
  218. package/lib/typescript/src/features/player/options.d.ts +21 -19
  219. package/lib/typescript/src/features/player/options.d.ts.map +1 -1
  220. package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
  221. package/lib/typescript/src/features/player/validateOptions.d.ts +1 -8
  222. package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -1
  223. package/lib/typescript/src/features/queue/activeTrack.d.ts +8 -1
  224. package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
  225. package/lib/typescript/src/features/queue/queue.d.ts +5 -2
  226. package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
  227. package/lib/typescript/src/types/browser-native.d.ts +2 -2
  228. package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
  229. package/lib/typescript/src/types/browser-nodes.d.ts +361 -126
  230. package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
  231. package/lib/typescript/src/types/browser.d.ts +33 -53
  232. package/lib/typescript/src/types/browser.d.ts.map +1 -1
  233. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +16 -17
  234. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
  235. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +24 -19
  236. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
  237. package/lib/typescript/src/utils/getTrackIdentity.d.ts +18 -0
  238. package/lib/typescript/src/utils/getTrackIdentity.d.ts.map +1 -0
  239. package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts +21 -0
  240. package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts.map +1 -0
  241. package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +0 -1
  242. package/nitrogen/generated/android/c++/{JFavoritesMatchMode.hpp → JArtworkRendering.hpp} +15 -15
  243. package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +8 -3
  244. package/nitrogen/generated/android/c++/JBrowseResult.hpp +16 -4
  245. package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
  246. package/nitrogen/generated/android/c++/JCardImage.hpp +58 -0
  247. package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +8 -3
  248. package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +8 -3
  249. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +8 -3
  250. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +8 -3
  251. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +8 -3
  252. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +8 -3
  253. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +8 -2
  254. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +8 -2
  255. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +16 -4
  256. package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +8 -3
  257. package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +2 -5
  258. package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +8 -3
  259. package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +15 -3
  260. package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +8 -2
  261. package/nitrogen/generated/android/c++/JGridTile.hpp +61 -0
  262. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +33 -14
  263. package/nitrogen/generated/android/c++/JIOSOptions.hpp +9 -3
  264. package/nitrogen/generated/android/c++/JImageShape.hpp +58 -0
  265. package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +8 -3
  266. package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +22 -10
  267. package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +14 -2
  268. package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +10 -7
  269. package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +2 -5
  270. package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +9 -3
  271. package/nitrogen/generated/android/c++/JOptions.hpp +2 -5
  272. package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +8 -3
  273. package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
  274. package/nitrogen/generated/android/c++/JResolvedTrack.hpp +55 -51
  275. package/nitrogen/generated/android/c++/JSection.hpp +120 -0
  276. package/nitrogen/generated/android/c++/JSectionStyle.hpp +96 -0
  277. package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
  278. package/nitrogen/generated/android/c++/JTrack.hpp +23 -48
  279. package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +8 -2
  280. package/nitrogen/generated/android/c++/JTrackStyle.hpp +46 -18
  281. package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +16 -4
  282. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → ArtworkRendering.kt} +5 -5
  283. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayPlayingIndicatorLocation.kt +23 -0
  284. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CardImage.kt +23 -0
  285. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/GridTile.kt +24 -0
  286. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +6 -3
  287. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageShape.kt +23 -0
  288. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +5 -5
  289. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +6 -3
  290. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
  291. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -18
  292. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → Section.kt} +18 -6
  293. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SectionStyle.kt +59 -0
  294. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StyleDisplay.kt +23 -0
  295. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +6 -15
  296. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +35 -5
  297. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +158 -82
  298. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +25 -10
  299. package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +25 -10
  300. package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
  301. package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
  302. package/nitrogen/generated/ios/swift/CardImage.swift +40 -0
  303. package/nitrogen/generated/ios/swift/GridTile.swift +44 -0
  304. package/nitrogen/generated/ios/swift/IOSOptions.swift +7 -2
  305. package/nitrogen/generated/ios/swift/ImageShape.swift +40 -0
  306. package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +5 -5
  307. package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +12 -1
  308. package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
  309. package/nitrogen/generated/ios/swift/ResolvedTrack.swift +54 -83
  310. package/nitrogen/generated/ios/swift/Section.swift +100 -0
  311. package/nitrogen/generated/ios/swift/SectionStyle.swift +133 -0
  312. package/nitrogen/generated/ios/swift/StyleDisplay.swift +40 -0
  313. package/nitrogen/generated/ios/swift/Track.swift +20 -73
  314. package/nitrogen/generated/ios/swift/TrackStyle.swift +86 -22
  315. package/nitrogen/generated/shared/c++/{FavoritesMatchMode.hpp → ArtworkRendering.hpp} +17 -17
  316. package/nitrogen/generated/shared/c++/CarPlayPlayingIndicatorLocation.hpp +76 -0
  317. package/nitrogen/generated/shared/c++/CardImage.hpp +76 -0
  318. package/nitrogen/generated/shared/c++/GridTile.hpp +80 -0
  319. package/nitrogen/generated/shared/c++/IOSOptions.hpp +9 -2
  320. package/nitrogen/generated/shared/c++/ImageShape.hpp +76 -0
  321. package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +6 -6
  322. package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +9 -2
  323. package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
  324. package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +30 -38
  325. package/nitrogen/generated/shared/c++/Section.hpp +106 -0
  326. package/nitrogen/generated/shared/c++/SectionStyle.hpp +126 -0
  327. package/nitrogen/generated/shared/c++/StyleDisplay.hpp +76 -0
  328. package/nitrogen/generated/shared/c++/Track.hpp +15 -31
  329. package/nitrogen/generated/shared/c++/TrackStyle.hpp +73 -34
  330. package/package.json +7 -2
  331. package/src/AudioBrowser.ts +1 -0
  332. package/src/features/browser-config.test.ts +21 -6
  333. package/src/features/browser-config.ts +11 -8
  334. package/src/features/browser.test.ts +1 -1
  335. package/src/features/browser.ts +2 -2
  336. package/src/features/favorites.ts +13 -10
  337. package/src/features/hook-wiring.test.ts +63 -15
  338. package/src/features/playback/controls.ts +4 -2
  339. package/src/features/playback/progress.ts +2 -1
  340. package/src/features/playback/rate.ts +8 -1
  341. package/src/features/playback/state.ts +2 -3
  342. package/src/features/playback/trackPlaybackTime.ts +1 -1
  343. package/src/features/player/options.ts +27 -19
  344. package/src/features/player/setup.test.ts +12 -3
  345. package/src/features/player/setup.ts +3 -1
  346. package/src/features/player/validateOptions.ts +2 -0
  347. package/src/features/queue/activeTrack.ts +14 -3
  348. package/src/features/queue/queue.ts +11 -4
  349. package/src/nitro-stub-drift.test.ts +40 -0
  350. package/src/public-surface.test.ts +40 -0
  351. package/src/specs/audio-browser.nitro.ts +4 -3
  352. package/src/style-resolution.test.ts +210 -0
  353. package/src/types/browser-native.ts +2 -2
  354. package/src/types/browser-nodes.ts +377 -135
  355. package/src/types/browser.ts +34 -55
  356. package/src/utils/LazyNativeEmitter.ts +20 -10
  357. package/src/utils/NativeUpdatedValue.ts +29 -13
  358. package/src/utils/getTrackIdentity.ts +20 -0
  359. package/src/utils/useNativeValueRefreshedBy.ts +38 -0
  360. package/src/web/NativeAudioBrowser.ts +37 -22
  361. package/src/web/TrackPlayer/QueueManager.test.ts +29 -0
  362. package/src/web/TrackPlayer/QueueManager.ts +14 -0
  363. package/src/web/browser/BrowserManager.ts +176 -95
  364. package/src/web/browser/FavoriteManager.test.ts +84 -0
  365. package/src/web/browser/FavoriteManager.ts +28 -23
  366. package/src/web/browser/assertBrowsePageShape.test.ts +1 -1
  367. package/src/web/browser/assertBrowsePageShape.ts +5 -3
  368. package/src/web/browser/classifyTrackNavigation.test.ts +10 -8
  369. package/src/web/browser/classifyTrackNavigation.ts +9 -9
  370. package/src/web/browser/parseSearchResponse.test.ts +8 -2
  371. package/src/web/browser/sections.test.ts +91 -0
  372. package/src/web/browser/sections.ts +72 -0
  373. package/src/web/player/NowPlayingManager.ts +4 -3
  374. package/src/web/util/BrowserPathHelper.test.ts +52 -0
  375. package/src/web/util/BrowserPathHelper.ts +61 -13
  376. package/website/guide/android-auto.md +114 -0
  377. package/website/guide/android-certificates.md +409 -0
  378. package/website/guide/artwork.md +255 -0
  379. package/website/guide/audio-output.md +135 -0
  380. package/website/guide/automotive.md +86 -0
  381. package/website/guide/basic-usage.md +151 -0
  382. package/website/guide/battery.md +115 -0
  383. package/website/guide/browser.md +581 -0
  384. package/website/guide/carplay.md +620 -0
  385. package/website/guide/configuration.md +173 -0
  386. package/website/guide/equalizer.md +231 -0
  387. package/website/guide/errors.md +310 -0
  388. package/website/guide/favorites.md +154 -0
  389. package/website/guide/gate.md +143 -0
  390. package/website/guide/getting-started.md +46 -0
  391. package/website/guide/hooks.md +238 -0
  392. package/website/guide/live-streams.md +125 -0
  393. package/website/guide/metadata.md +239 -0
  394. package/website/guide/migrating-from-track-player.md +244 -0
  395. package/website/guide/native-callback-fetch.md +127 -0
  396. package/website/guide/network.md +86 -0
  397. package/website/guide/now-playing.md +175 -0
  398. package/website/guide/playback.md +328 -0
  399. package/website/guide/queue.md +300 -0
  400. package/website/guide/remote-controls.md +256 -0
  401. package/website/guide/search.md +255 -0
  402. package/website/guide/sleep-timer.md +268 -0
  403. package/website/guide/track.md +246 -0
  404. package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
  405. package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
  406. package/nitrogen/generated/android/c++/JImageRowItem.hpp +0 -105
  407. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
  408. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
  409. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +0 -68
  410. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
  411. package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
  412. package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
  413. package/nitrogen/generated/ios/swift/ImageRowItem.swift +0 -195
  414. package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
  415. package/nitrogen/generated/shared/c++/FavoriteConfig.hpp +0 -84
  416. package/nitrogen/generated/shared/c++/ImageRowItem.hpp +0 -129
@@ -0,0 +1,277 @@
1
+ package com.audiobrowser.browser
2
+
3
+ import com.audiobrowser.TestFixtures.browseTrack
4
+ import com.audiobrowser.TestFixtures.section
5
+ import com.audiobrowser.TestFixtures.sectionStyle
6
+ import com.audiobrowser.TestFixtures.track
7
+ import com.audiobrowser.TestFixtures.trackStyle
8
+ import com.margelo.nitro.audiobrowser.ArtworkRendering
9
+ import com.margelo.nitro.audiobrowser.CardImage
10
+ import com.margelo.nitro.audiobrowser.GridTile
11
+ import com.margelo.nitro.audiobrowser.ImageShape
12
+ import com.margelo.nitro.audiobrowser.Section
13
+ import com.margelo.nitro.audiobrowser.SectionStyle
14
+ import com.margelo.nitro.audiobrowser.StyleDisplay
15
+ import org.junit.Assert.assertEquals
16
+ import org.junit.Assert.assertTrue
17
+ import org.junit.Test
18
+
19
+ /**
20
+ * The dev diagnostic for declarations that can never render (ADR 0011): the recovery for the
21
+ * compile-time invalid-combination errors the block model deliberately gave up. Structural
22
+ * inertness only — a surface that can't draw a property it understands is intended usage, never a
23
+ * finding. Mirrors `ios/Tests/InertStyleDiagnosticTests.swift`.
24
+ */
25
+ class InertStyleDiagnosticTest {
26
+
27
+ private fun findings(
28
+ sections: List<Section>,
29
+ page: SectionStyle? = null,
30
+ path: String = "/home",
31
+ ) = InertStyleDiagnostic.findings(path, page, sections)
32
+
33
+ // Container properties
34
+
35
+ @Test
36
+ fun `gridWrap on a list section is inert`() {
37
+ val found = findings(listOf(section(style = sectionStyle(gridWrap = false), title = "Recent")))
38
+ assertEquals(1, found.size)
39
+ assertTrue(found[0], found[0].contains("section 'Recent'"))
40
+ assertTrue(found[0], found[0].contains("style.gridWrap"))
41
+ assertTrue(found[0], found[0].contains("display 'list'"))
42
+ assertTrue(found[0], found[0].contains("/home"))
43
+ }
44
+
45
+ @Test
46
+ fun `gridWrap in a grid is live`() {
47
+ val style = sectionStyle(gridWrap = false, display = StyleDisplay.GRID)
48
+ assertTrue(findings(listOf(section(style = style))).isEmpty())
49
+ }
50
+
51
+ @Test
52
+ fun `a section inherits the page's grid display`() {
53
+ // `display` resolves by scope override, so the page's grid makes the section's `gridWrap` live
54
+ // without the section restating it.
55
+ val found =
56
+ findings(
57
+ listOf(section(style = sectionStyle(gridWrap = false))),
58
+ page = sectionStyle(display = StyleDisplay.GRID),
59
+ )
60
+ assertTrue(found.isEmpty())
61
+ }
62
+
63
+ @Test
64
+ fun `gridTile on a list section is inert`() {
65
+ val found = findings(listOf(section(style = sectionStyle(gridTile = GridTile.CARD))))
66
+ assertEquals(1, found.size)
67
+ assertTrue(found[0], found[0].contains("style.gridTile"))
68
+ assertTrue(found[0], found[0].contains("section #1"))
69
+ }
70
+
71
+ // Item properties
72
+
73
+ @Test
74
+ fun `card properties need the card treatment`() {
75
+ val plainGrid =
76
+ sectionStyle(
77
+ display = StyleDisplay.GRID,
78
+ cardTint = "#1e3a8a",
79
+ cardImage = CardImage.BACKGROUND,
80
+ )
81
+ val found = findings(listOf(section(style = plainGrid, title = "Featured")))
82
+ assertEquals(2, found.size)
83
+ assertTrue(found.any { it.contains("style.cardTint") })
84
+ assertTrue(found.any { it.contains("style.cardImage") })
85
+ assertTrue(found.all { it.contains("display 'grid', gridTile 'plain'") })
86
+ }
87
+
88
+ @Test
89
+ fun `card properties in a card grid are live`() {
90
+ val cards =
91
+ sectionStyle(
92
+ display = StyleDisplay.GRID,
93
+ gridTile = GridTile.CARD,
94
+ cardTint = "#1e3a8a",
95
+ cardImage = CardImage.BACKGROUND,
96
+ )
97
+ assertTrue(findings(listOf(section(style = cards))).isEmpty())
98
+ }
99
+
100
+ @Test
101
+ fun `imageShape needs a shaped tile`() {
102
+ val list = sectionStyle(imageShape = ImageShape.CIRCULAR)
103
+ val cards =
104
+ sectionStyle(
105
+ display = StyleDisplay.GRID,
106
+ gridTile = GridTile.CARD,
107
+ imageShape = ImageShape.CIRCULAR,
108
+ )
109
+ val condensed =
110
+ sectionStyle(
111
+ display = StyleDisplay.GRID,
112
+ gridTile = GridTile.CONDENSED,
113
+ imageShape = ImageShape.CIRCULAR,
114
+ )
115
+ assertEquals(1, findings(listOf(section(style = list))).size)
116
+ assertEquals(1, findings(listOf(section(style = cards))).size)
117
+ // Plain and condensed tiles both take a shape.
118
+ assertTrue(findings(listOf(section(style = condensed))).isEmpty())
119
+ }
120
+
121
+ @Test
122
+ fun `accessorySymbol is inert only on cards`() {
123
+ val rows = sectionStyle(accessorySymbol = "lock.fill")
124
+ val cards =
125
+ sectionStyle(
126
+ display = StyleDisplay.GRID,
127
+ gridTile = GridTile.CARD,
128
+ accessorySymbol = "lock.fill",
129
+ )
130
+ // List rows draw accessories; card elements have no slot for one.
131
+ assertTrue(findings(listOf(section(style = rows))).isEmpty())
132
+ val found = findings(listOf(section(style = cards)))
133
+ assertEquals(1, found.size)
134
+ assertTrue(found[0], found[0].contains("no accessory slot"))
135
+ }
136
+
137
+ @Test
138
+ fun `artworkRendering is never inert`() {
139
+ val stencilled =
140
+ sectionStyle(
141
+ display = StyleDisplay.GRID,
142
+ gridTile = GridTile.CARD,
143
+ artworkRendering = ArtworkRendering.STENCIL,
144
+ )
145
+ assertTrue(findings(listOf(section(style = stencilled))).isEmpty())
146
+ }
147
+
148
+ // Track-level declarations
149
+
150
+ @Test
151
+ fun `track declarations are reported per section`() {
152
+ val results =
153
+ section(
154
+ style = sectionStyle(display = StyleDisplay.GRID),
155
+ title = "Results",
156
+ children =
157
+ arrayOf(
158
+ track(style = trackStyle(cardTint = "#111")),
159
+ track(src = "https://s/b.mp3", style = trackStyle(cardTint = "#222")),
160
+ track(src = "https://s/c.mp3"),
161
+ ),
162
+ )
163
+ val found = findings(listOf(results))
164
+ assertEquals(1, found.size)
165
+ assertTrue(found[0], found[0].contains("2 tracks in section 'Results'"))
166
+ assertTrue(found[0], found[0].contains("style.cardTint"))
167
+ }
168
+
169
+ @Test
170
+ fun `a track's own shape is live in a plain grid`() {
171
+ val grid =
172
+ section(
173
+ style = sectionStyle(display = StyleDisplay.GRID),
174
+ children = arrayOf(track(style = trackStyle(imageShape = ImageShape.CIRCULAR))),
175
+ )
176
+ assertTrue(findings(listOf(grid)).isEmpty())
177
+ }
178
+
179
+ // The page block
180
+
181
+ @Test
182
+ fun `a page declaration live in one section is not reported`() {
183
+ // Card tint reaches both sections; one renders cards. Live is live.
184
+ val found =
185
+ findings(
186
+ listOf(
187
+ section(style = sectionStyle(display = StyleDisplay.GRID, gridTile = GridTile.CARD)),
188
+ section(style = sectionStyle(display = StyleDisplay.GRID)),
189
+ ),
190
+ page = sectionStyle(cardTint = "#1e3a8a"),
191
+ )
192
+ assertTrue(found.isEmpty())
193
+ }
194
+
195
+ @Test
196
+ fun `a page declaration no section renders is reported once`() {
197
+ val found =
198
+ findings(
199
+ listOf(
200
+ section(style = sectionStyle(display = StyleDisplay.GRID), title = "A"),
201
+ section(title = "B"),
202
+ ),
203
+ page = sectionStyle(cardTint = "#1e3a8a"),
204
+ )
205
+ assertEquals(1, found.size)
206
+ assertTrue(found[0], found[0].contains("the page block"))
207
+ assertTrue(found[0], found[0].contains("no section that inherits it"))
208
+ }
209
+
210
+ @Test
211
+ fun `a section override shadows the page value`() {
212
+ // Every section declares its own tint, so the page's is never resolved — shadowed, not inert;
213
+ // the diagnostic reports what can't render, not what isn't reached.
214
+ val found =
215
+ findings(
216
+ listOf(
217
+ section(
218
+ style =
219
+ sectionStyle(display = StyleDisplay.GRID, gridTile = GridTile.CARD, cardTint = "#000")
220
+ )
221
+ ),
222
+ page = sectionStyle(cardTint = "#1e3a8a"),
223
+ )
224
+ assertTrue(found.isEmpty())
225
+ }
226
+
227
+ @Test
228
+ fun `a page-wide grid makes section declarations live`() {
229
+ val found =
230
+ findings(
231
+ listOf(section(style = sectionStyle(cardTint = "#111"))),
232
+ page = sectionStyle(display = StyleDisplay.GRID, gridTile = GridTile.CARD),
233
+ )
234
+ assertTrue(found.isEmpty())
235
+ }
236
+
237
+ // The positional `display`
238
+
239
+ @Test
240
+ fun `display on a playable track is inert`() {
241
+ val tracks =
242
+ section(
243
+ title = "Tracks",
244
+ children =
245
+ arrayOf(
246
+ track(style = trackStyle(display = StyleDisplay.GRID)),
247
+ browseTrack(style = trackStyle(display = StyleDisplay.GRID)),
248
+ ),
249
+ )
250
+ val found = findings(listOf(tracks))
251
+ assertEquals(1, found.size)
252
+ assertTrue(found[0], found[0].contains("1 track in section 'Tracks'"))
253
+ assertTrue(found[0], found[0].contains("style.display"))
254
+ assertTrue(found[0], found[0].contains("opens none"))
255
+ }
256
+
257
+ @Test
258
+ fun `display on a section or page is never inert`() {
259
+ val found =
260
+ findings(
261
+ listOf(
262
+ section(
263
+ style = sectionStyle(display = StyleDisplay.LIST),
264
+ children = arrayOf(browseTrack()),
265
+ )
266
+ ),
267
+ page = sectionStyle(display = StyleDisplay.GRID),
268
+ )
269
+ assertTrue(found.isEmpty())
270
+ }
271
+
272
+ @Test
273
+ fun `an unstyled page is silent`() {
274
+ assertTrue(findings(listOf(section(children = arrayOf(track(), browseTrack())))).isEmpty())
275
+ assertTrue(findings(emptyList()).isEmpty())
276
+ }
277
+ }
@@ -35,7 +35,7 @@ class JsonArtworkTest {
35
35
  // so one unparseable row rejected the whole page rather than losing an image.
36
36
  val resolved =
37
37
  json.decodeFromString<JsonResolvedTrack>(
38
- """{"url":"/favorites","title":"Favorites","children":[
38
+ """{"path":"/favorites","title":"Favorites","children":[
39
39
  {"title":"Playlist","artwork":{"light":"https://e.example/l.png","dark":"https://e.example/d.png"}}
40
40
  ]}"""
41
41
  )
@@ -0,0 +1,147 @@
1
+ package com.audiobrowser.browser
2
+
3
+ import com.margelo.nitro.audiobrowser.ArtworkRendering
4
+ import com.margelo.nitro.audiobrowser.CardImage
5
+ import com.margelo.nitro.audiobrowser.GridTile
6
+ import com.margelo.nitro.audiobrowser.ImageShape
7
+ import com.margelo.nitro.audiobrowser.StyleDisplay
8
+ import kotlinx.serialization.json.Json
9
+ import org.junit.Assert.assertEquals
10
+ import org.junit.Assert.assertNull
11
+ import org.junit.Test
12
+
13
+ /** Style blocks on the wire, decoded tolerantly (ADR 0011). */
14
+ class JsonStyleTest {
15
+ private val json = Json { ignoreUnknownKeys = true }
16
+
17
+ @Test
18
+ fun `decodes a block onto TrackStyle`() {
19
+ val track =
20
+ json.decodeFromString<JsonTrack>(
21
+ """{"title":"X","style":{"display":"grid","artworkRendering":"stencil"}}"""
22
+ )
23
+ val style = track.toNitro().style
24
+ assertEquals(StyleDisplay.GRID, style?.display)
25
+ assertEquals(ArtworkRendering.STENCIL, style?.artworkRendering)
26
+ }
27
+
28
+ @Test
29
+ fun `decodes a block onto SectionStyle`() {
30
+ val resolved =
31
+ json.decodeFromString<JsonResolvedTrack>(
32
+ """{"path":"/home","title":"Home","sections":[
33
+ {"style":{"display":"grid","gridWrap":false},"children":[{"title":"C","src":"s"}]}
34
+ ]}"""
35
+ )
36
+ val style = resolved.toNitro().sections?.first()?.style
37
+ assertEquals(StyleDisplay.GRID, style?.display)
38
+ assertEquals(false, style?.gridWrap)
39
+ }
40
+
41
+ @Test
42
+ fun `a stale string style decodes as no declaration - never a parse failure`() {
43
+ // The retired string vocabulary must never kill the page (tolerant
44
+ // decoding, ADR 0011).
45
+ val resolved =
46
+ json.decodeFromString<JsonResolvedTrack>(
47
+ """{"path":"/home","title":"Home","style":"rail","children":[
48
+ {"title":"C","src":"s","style":"grid"}
49
+ ]}"""
50
+ )
51
+ val nitro = resolved.toNitro()
52
+ assertNull(nitro.style)
53
+ assertNull(nitro.children?.first()?.style)
54
+ }
55
+
56
+ @Test
57
+ fun `unknown enum values decode as no declaration`() {
58
+ // All-unknown declarations collapse to "no block" — one shape for "no
59
+ // declaration" on every platform.
60
+ val track =
61
+ json.decodeFromString<JsonTrack>(
62
+ """{"title":"X","style":{"display":"carousel","artworkRendering":"embossed","gridWrap":"yes"}}"""
63
+ )
64
+ assertNull(track.toNitro().style)
65
+ }
66
+
67
+ @Test
68
+ fun `an empty block decodes as no declaration`() {
69
+ val track = json.decodeFromString<JsonTrack>("""{"title":"X","style":{}}""")
70
+ assertNull(track.toNitro().style)
71
+ }
72
+
73
+ @Test
74
+ fun `a quoted boolean gridWrap is a wrong-typed field - no declaration`() {
75
+ // iOS's strict-typed tolerant decode drops a quoted "false"; the lenient
76
+ // Kotlin mapper must not read it either, or the same payload renders a
77
+ // teaser shelf on one platform and a wrapping grid on the other.
78
+ val resolved =
79
+ json.decodeFromString<JsonResolvedTrack>(
80
+ """{"path":"/home","title":"Home","sections":[
81
+ {"style":{"display":"grid","gridWrap":"false"},"children":[{"title":"C","src":"s"}]}
82
+ ]}"""
83
+ )
84
+ val style = resolved.toNitro().sections?.first()?.style
85
+ assertEquals(StyleDisplay.GRID, style?.display)
86
+ assertNull(style?.gridWrap)
87
+ }
88
+
89
+ @Test
90
+ fun `decodes disabled`() {
91
+ val track = json.decodeFromString<JsonTrack>("""{"title":"X","src":"s","disabled":true}""")
92
+ assertEquals(true, track.toNitro().disabled)
93
+ }
94
+
95
+ @Test
96
+ fun `decodes imageShape and accessorySymbol`() {
97
+ val track =
98
+ json.decodeFromString<JsonTrack>(
99
+ """{"title":"X","style":{"imageShape":"circular","accessorySymbol":"lock.fill"}}"""
100
+ )
101
+ val style = track.toNitro().style
102
+ assertEquals(ImageShape.CIRCULAR, style?.imageShape)
103
+ assertEquals("lock.fill", style?.accessorySymbol)
104
+ }
105
+
106
+ @Test
107
+ fun `accessorySymbol 'none' is a value - not emptiness`() {
108
+ // The inheritance-escape sentinel must survive the wire — collapsing it
109
+ // to "no declaration" would let the section value flow back in.
110
+ val track =
111
+ json.decodeFromString<JsonTrack>("""{"title":"X","style":{"accessorySymbol":"none"}}""")
112
+ assertEquals("none", track.toNitro().style?.accessorySymbol)
113
+ }
114
+
115
+ @Test
116
+ fun `unknown imageShape decodes as no declaration`() {
117
+ val track =
118
+ json.decodeFromString<JsonTrack>("""{"title":"X","style":{"imageShape":"hexagonal"}}""")
119
+ assertNull(track.toNitro().style)
120
+ }
121
+
122
+ @Test
123
+ fun `decodes the card family`() {
124
+ val resolved =
125
+ json.decodeFromString<JsonResolvedTrack>(
126
+ """{"path":"/home","title":"Home","sections":[
127
+ {"style":{"display":"grid","gridTile":"card","cardTint":"#1e3a8a","cardImage":"background"},
128
+ "children":[{"title":"C","src":"s"}]}
129
+ ]}"""
130
+ )
131
+ val style = resolved.toNitro().sections?.first()?.style
132
+ assertEquals(GridTile.CARD, style?.gridTile)
133
+ assertEquals("#1e3a8a", style?.cardTint)
134
+ assertEquals(CardImage.BACKGROUND, style?.cardImage)
135
+ }
136
+
137
+ @Test
138
+ fun `unknown gridTile decodes as no declaration`() {
139
+ val resolved =
140
+ json.decodeFromString<JsonResolvedTrack>(
141
+ """{"path":"/home","title":"Home","sections":[
142
+ {"style":{"gridTile":"hexagon"},"children":[{"title":"C","src":"s"}]}
143
+ ]}"""
144
+ )
145
+ assertNull(resolved.toNitro().sections?.first()?.style)
146
+ }
147
+ }
@@ -1,94 +1,169 @@
1
1
  package com.audiobrowser.browser
2
2
 
3
- import com.audiobrowser.TestFixtures.imageRowItem
3
+ import com.audiobrowser.TestFixtures.resolvedTrack
4
+ import com.audiobrowser.TestFixtures.section
5
+ import com.audiobrowser.TestFixtures.sectionStyle
4
6
  import com.audiobrowser.TestFixtures.track
7
+ import com.margelo.nitro.audiobrowser.StyleDisplay
5
8
  import org.junit.Assert.assertEquals
6
9
  import org.junit.Assert.assertNull
7
10
  import org.junit.Test
8
11
 
9
12
  class SectionScopeTest {
10
13
 
11
- @Test
12
- fun `scopes to the contiguous groupTitle run`() {
13
- val children =
14
- listOf(
15
- track(id = "a", src = "a", groupTitle = "First"),
16
- track(id = "b", src = "b", groupTitle = "First"),
17
- track(id = "c", src = "c", groupTitle = "Second"),
18
- track(id = "d", src = "d", groupTitle = "Second"),
19
- track(id = "e", src = "e"),
20
- )
14
+ // Two sections sharing an identity — the duplicate-heavy shape the flat model
15
+ // had to disambiguate by precedence; sections + the stamped flat index make
16
+ // it exact.
17
+ private val sections =
18
+ listOf(
19
+ section(title = "First", children = arrayOf(track(src = "dup"), track(src = "x"))),
20
+ section(
21
+ title = "Second",
22
+ children = arrayOf(track(src = "y"), track(src = "dup"), track(src = "z")),
23
+ ),
24
+ )
21
25
 
22
- val section = SectionScope.section(children, "c") as SectionScope.Section.Run
23
- assertEquals(listOf("c", "d"), section.tracks.map { it.src })
26
+ @Test
27
+ fun `falls back to the first section containing the identity`() {
28
+ val scoped = SectionScope.scoped(sections, "dup")!!
29
+ assertEquals(listOf("dup", "x"), scoped.section.children.map { it.src })
30
+ assertNull(scoped.tappedOffset)
31
+ }
24
32
 
25
- val first = SectionScope.section(children, "a") as SectionScope.Section.Run
26
- assertEquals(listOf("a", "b"), first.tracks.map { it.src })
33
+ @Test
34
+ fun `flat index pins the tapped section`() {
35
+ // Flat index 3 = second section, offset 1.
36
+ val scoped = SectionScope.scoped(sections, "dup", tappedIndex = 3)!!
37
+ assertEquals(listOf("y", "dup", "z"), scoped.section.children.map { it.src })
38
+ assertEquals(1, scoped.tappedOffset)
27
39
  }
28
40
 
29
41
  @Test
30
- fun `ungrouped items form their own run`() {
31
- val children =
42
+ fun `flat index pins the exact copy within a section`() {
43
+ val playlist =
32
44
  listOf(
33
- track(src = "a", groupTitle = "First"),
34
- track(src = "b"),
35
- track(src = "c"),
36
- track(src = "d", groupTitle = "Second"),
45
+ section(
46
+ children = arrayOf(track(src = "a"), track(src = "b"), track(src = "a"), track(src = "c"))
47
+ )
37
48
  )
49
+ val scoped = SectionScope.scoped(playlist, "a", tappedIndex = 2)!!
50
+ assertEquals(2, scoped.tappedOffset)
51
+ }
38
52
 
39
- val section = SectionScope.section(children, "b") as SectionScope.Section.Run
40
- assertEquals(listOf("b", "c"), section.tracks.map { it.src })
53
+ @Test
54
+ fun `stale index falls back to the first identity match`() {
55
+ // The child at the stamped index no longer carries the tapped identity
56
+ // (the list shifted) — resolution ignores the index and pins nothing.
57
+ val scoped = SectionScope.scoped(sections, "dup", tappedIndex = 1)!!
58
+ assertEquals(listOf("dup", "x"), scoped.section.children.map { it.src })
59
+ assertNull(scoped.tappedOffset)
41
60
  }
42
61
 
43
62
  @Test
44
- fun `identical titles in separate runs stay separate`() {
63
+ fun `out-of-range index is ignored`() {
64
+ val scoped = SectionScope.scoped(sections, "dup", tappedIndex = 99)!!
65
+ assertEquals(listOf("dup", "x"), scoped.section.children.map { it.src })
66
+ assertNull(scoped.tappedOffset)
67
+ }
68
+
69
+ @Test
70
+ fun `vanished identity returns null`() {
71
+ assertNull(SectionScope.scoped(sections, "gone"))
72
+ }
73
+
74
+ @Test
75
+ fun `locates a track by its id when it has one`() {
76
+ // Identity rule (ADR 0008): the id wins over the src, so a __trackId stamped
77
+ // from an id-bearing track must resolve — and its src must not.
45
78
  val children =
46
- listOf(
47
- track(src = "a", groupTitle = "Same"),
48
- track(src = "x", groupTitle = "Other"),
49
- track(src = "b", groupTitle = "Same"),
79
+ arrayOf(
80
+ track(id = "stable-a", src = "https://s/a.mp3"),
81
+ track(id = "stable-b", src = "https://s/b.mp3"),
50
82
  )
83
+ val scoped = SectionScope.scoped(listOf(section(children = children)), "stable-b")!!
84
+ assertEquals(listOf("stable-a", "stable-b"), scoped.section.children.map { it.id })
51
85
 
52
- val section = SectionScope.section(children, "a") as SectionScope.Section.Run
53
- assertEquals(listOf("a"), section.tracks.map { it.src })
86
+ // The src is shadowed by the id — it is not the track's identity.
87
+ assertNull(SectionScope.scoped(listOf(section(children = children)), "https://s/b.mp3"))
54
88
  }
55
89
 
90
+ // MARK: page shape helpers
91
+
56
92
  @Test
57
- fun `finds the id inside an image row`() {
58
- val items = arrayOf(imageRowItem("s1"), imageRowItem("s2"))
59
- val row = track(title = "Most Played", src = null, imageRow = items)
60
- val children = listOf(row, track(src = "a"))
93
+ fun `children sugar normalizes to one untitled section`() {
94
+ val page = resolvedTrack(children = arrayOf(track(src = "a"), track(src = "b")))
95
+ val sections = page.normalizedSections!!
96
+ assertEquals(1, sections.size)
97
+ assertNull(sections[0].title)
98
+ assertEquals(listOf("a", "b"), sections[0].children.map { it.src })
99
+ }
100
+
101
+ @Test
102
+ fun `sections win over children`() {
103
+ val page =
104
+ resolvedTrack(
105
+ sections = arrayOf(section(title = "S", children = arrayOf(track(src = "a")))),
106
+ children = arrayOf(track(src = "zzz")),
107
+ )
108
+ val sections = page.normalizedSections!!
109
+ assertEquals(1, sections.size)
110
+ assertEquals("S", sections[0].title)
111
+ }
61
112
 
62
- val section = SectionScope.section(children, "s2") as SectionScope.Section.ImageRow
63
- assertEquals(listOf("s1", "s2"), section.items.map { it.src })
113
+ @Test
114
+ fun `flattened children concatenate in section order`() {
115
+ val page =
116
+ resolvedTrack(
117
+ sections =
118
+ arrayOf(
119
+ section(title = "A", children = arrayOf(track(src = "a1"), track(src = "a2"))),
120
+ section(children = arrayOf(track(src = "b1"))),
121
+ )
122
+ )
123
+ assertEquals(listOf("a1", "a2", "b1"), page.flattenedChildren!!.map { it.src })
64
124
  }
65
125
 
66
126
  @Test
67
- fun `unknown id returns null`() {
68
- assertNull(SectionScope.section(listOf(track(src = "a")), "zz"))
127
+ fun `childless page has no sections`() {
128
+ assertNull(resolvedTrack().normalizedSections)
69
129
  }
70
130
 
71
- // Pins the documented precedence: sections are located by src, and an id
72
- // present in both an image row and the flat list resolves to the row.
131
+ // --- styleResolvedSections: the page->section fold (ADR 0011) ---
132
+
73
133
  @Test
74
- fun `image row wins over a flat-list duplicate`() {
75
- val row = track(title = "Row", src = null, imageRow = arrayOf(imageRowItem("dup")))
76
- val children = listOf(row, track(src = "dup"), track(src = "b"))
134
+ fun `an unstyled section reaches the surface with the page's block folded in`() {
135
+ // The end-to-end wire for ResolvedTrack.style: without this fold, the
136
+ // page tier is a dead field — every existing unit test would stay green
137
+ // while a whole tier of ADR 0011 went dark in production.
138
+ val page =
139
+ resolvedTrack(
140
+ style =
141
+ sectionStyle(display = StyleDisplay.GRID, artworkRendering = null, gridWrap = false),
142
+ sections = arrayOf(section(children = arrayOf(track(src = "a")))),
143
+ )
77
144
 
78
- val section = SectionScope.section(children, "dup") as SectionScope.Section.ImageRow
79
- assertEquals(listOf("dup"), section.items.map { it.src })
145
+ val folded = page.styleResolvedSections()!!.single()
146
+ assertEquals(StyleDisplay.GRID, folded.style?.display)
147
+ assertEquals(false, folded.style?.gridWrap)
148
+ // The extras leg of this wire — folded section → per-item grid hint — is
149
+ // asserted in MediaExtrasBuilderTest (it needs Robolectric for Bundle).
80
150
  }
81
151
 
82
152
  @Test
83
- fun `duplicate src across runs resolves to the earlier run`() {
84
- val children =
85
- listOf(
86
- track(src = "dup", groupTitle = "First"),
87
- track(src = "x", groupTitle = "Second"),
88
- track(src = "dup", groupTitle = "Second"),
153
+ fun `a section's own block overrides the page's in the fold`() {
154
+ val page =
155
+ resolvedTrack(
156
+ style = sectionStyle(display = StyleDisplay.GRID, artworkRendering = null, gridWrap = null),
157
+ sections =
158
+ arrayOf(
159
+ section(
160
+ style =
161
+ sectionStyle(display = StyleDisplay.LIST, artworkRendering = null, gridWrap = null),
162
+ children = arrayOf(track(src = "a")),
163
+ )
164
+ ),
89
165
  )
90
166
 
91
- val section = SectionScope.section(children, "dup") as SectionScope.Section.Run
92
- assertEquals(listOf("dup"), section.tracks.map { it.src })
167
+ assertEquals(StyleDisplay.LIST, page.styleResolvedSections()!!.single().style?.display)
93
168
  }
94
169
  }