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.
- package/README.md +4 -4
- package/android/build.gradle +6 -5
- package/android/gradle.properties +1 -1
- package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +110 -25
- package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +254 -188
- package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +23 -3
- package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +77 -58
- package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +68 -26
- package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +49 -0
- package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +9 -7
- package/android/src/main/java/com/audiobrowser/extension/TappedTrack.kt +20 -0
- package/android/src/main/java/com/audiobrowser/extension/TrackIdentity.kt +15 -0
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +120 -67
- package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +21 -24
- package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +93 -73
- package/android/src/main/java/com/audiobrowser/player/Player.kt +48 -30
- package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
- package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +46 -5
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingExtendedTrustManager.kt +86 -27
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +58 -14
- package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +20 -6
- package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +249 -86
- package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +44 -40
- package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +28 -23
- package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +35 -0
- package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +45 -53
- package/android/src/test/java/com/audiobrowser/TestFixtures.kt +69 -30
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +6 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +345 -8
- package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +38 -0
- package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +1 -1
- package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +91 -0
- package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -53
- package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +87 -0
- package/android/src/test/java/com/audiobrowser/extension/TappedTrackTest.kt +43 -0
- package/android/src/test/java/com/audiobrowser/extension/TrackIdentityTest.kt +41 -0
- package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +1 -2
- package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +42 -2
- package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +55 -0
- package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +152 -8
- package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +218 -10
- package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherCallSiteTest.kt +322 -0
- package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +41 -16
- package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +161 -0
- package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +7 -7
- package/ios/Browser/BrowserConfig.swift +102 -5
- package/ios/Browser/BrowserManager+URLResolution.swift +18 -14
- package/ios/Browser/BrowserManager.swift +131 -143
- package/ios/Browser/BrowserPathHelper.swift +44 -44
- package/ios/Browser/JsonModels.swift +187 -106
- package/ios/Browser/SearchDrillIn.swift +6 -6
- package/ios/Browser/SectionPresentation.swift +34 -0
- package/ios/Browser/SectionScope.swift +58 -24
- package/ios/Browser/StyleResolver.swift +47 -0
- package/ios/Browser/TabBarEntries.swift +2 -2
- package/ios/Browser/TrackIdTemplate.swift +37 -0
- package/ios/CarPlay/CarPlayArtworkResolver.swift +8 -7
- package/ios/CarPlay/CarPlayController.swift +105 -67
- package/ios/CarPlay/CarPlayImageLoader.swift +38 -18
- package/ios/CarPlay/CarPlayListItemFactory.swift +217 -84
- package/ios/CarPlay/CarPlayNowPlayingManager.swift +15 -12
- package/ios/Extension/Capability+RemoteCommand.swift +1 -1
- package/ios/Extension/PlayerCapabilities+Favorite.swift +1 -15
- package/ios/Extension/ResolvedTrack+Copying.swift +11 -15
- package/ios/Extension/Section+Copying.swift +31 -0
- package/ios/Extension/Track+Copying.swift +6 -12
- package/ios/Extension/Track+Identity.swift +18 -0
- package/ios/HybridAudioBrowser.swift +113 -35
- package/ios/Model/NitroTypeStubs.swift +64 -23
- package/ios/Model/PlayerUpdateOptions.swift +7 -0
- package/ios/Player/NowPlayingUpdater.swift +1 -1
- package/ios/Player/PlaybackCoordinator.swift +2 -2
- package/ios/Player/RetryManager.swift +4 -3
- package/ios/Tests/BrowserPathHelperTests.swift +50 -20
- package/ios/Tests/CarPlayArtworkResolverTests.swift +24 -41
- package/ios/Tests/JsonModelsTests.swift +75 -1
- package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
- package/ios/Tests/QueueManagerTests.swift +4 -4
- package/ios/Tests/ResolvedTrackCopyingTests.swift +37 -35
- package/ios/Tests/SearchDrillInTests.swift +16 -16
- package/ios/Tests/SectionPresentationTests.swift +61 -0
- package/ios/Tests/SectionScopeTests.swift +87 -55
- package/ios/Tests/StyleResolverTests.swift +67 -0
- package/ios/Tests/TabBarEntriesTests.swift +18 -18
- package/ios/Tests/TrackIdTemplateTests.swift +67 -0
- package/ios/Tests/TrackIdentityTests.swift +20 -0
- package/ios/Tests/TrackSelectorTests.swift +168 -33
- package/ios/TrackSelector.swift +45 -30
- package/ios/Util/ArtworkImageFetcher.swift +16 -1
- package/lib/commonjs/AudioBrowser.js +11 -0
- package/lib/commonjs/AudioBrowser.js.map +1 -1
- package/lib/commonjs/features/browser-config.js +11 -8
- package/lib/commonjs/features/browser-config.js.map +1 -1
- package/lib/commonjs/features/browser.js +2 -2
- package/lib/commonjs/features/favorites.js +13 -10
- package/lib/commonjs/features/favorites.js.map +1 -1
- package/lib/commonjs/features/playback/controls.js +4 -2
- package/lib/commonjs/features/playback/controls.js.map +1 -1
- package/lib/commonjs/features/playback/state.js +2 -3
- package/lib/commonjs/features/playback/state.js.map +1 -1
- package/lib/commonjs/features/player/options.js.map +1 -1
- package/lib/commonjs/features/player/setup.js +3 -1
- package/lib/commonjs/features/player/setup.js.map +1 -1
- package/lib/commonjs/features/player/validateOptions.js +2 -0
- package/lib/commonjs/features/player/validateOptions.js.map +1 -1
- package/lib/commonjs/features/queue/activeTrack.js +14 -3
- package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
- package/lib/commonjs/features/queue/queue.js +11 -4
- package/lib/commonjs/features/queue/queue.js.map +1 -1
- package/lib/commonjs/utils/LazyNativeEmitter.js +21 -10
- package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/commonjs/utils/NativeUpdatedValue.js +31 -13
- package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/commonjs/utils/getTrackIdentity.js +24 -0
- package/lib/commonjs/utils/getTrackIdentity.js.map +1 -0
- package/lib/commonjs/utils/useNativeValueRefreshedBy.js +33 -0
- package/lib/commonjs/utils/useNativeValueRefreshedBy.js.map +1 -0
- package/lib/commonjs/web/NativeAudioBrowser.js +32 -24
- package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
- package/lib/commonjs/web/browser/BrowserManager.js +144 -87
- package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
- package/lib/commonjs/web/browser/FavoriteManager.js +30 -21
- package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
- package/lib/commonjs/web/browser/assertBrowsePageShape.js +2 -2
- package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -1
- package/lib/commonjs/web/browser/classifyTrackNavigation.js +9 -9
- package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -1
- package/lib/commonjs/web/browser/sections.js +89 -0
- package/lib/commonjs/web/browser/sections.js.map +1 -0
- package/lib/commonjs/web/player/NowPlayingManager.js +4 -3
- package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
- package/lib/commonjs/web/util/BrowserPathHelper.js +56 -11
- package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
- package/lib/module/AudioBrowser.js +1 -0
- package/lib/module/AudioBrowser.js.map +1 -1
- package/lib/module/features/browser-config.js +11 -8
- package/lib/module/features/browser-config.js.map +1 -1
- package/lib/module/features/browser.js +2 -2
- package/lib/module/features/favorites.js +13 -10
- package/lib/module/features/favorites.js.map +1 -1
- package/lib/module/features/playback/controls.js +4 -2
- package/lib/module/features/playback/controls.js.map +1 -1
- package/lib/module/features/playback/state.js +2 -3
- package/lib/module/features/playback/state.js.map +1 -1
- package/lib/module/features/player/options.js.map +1 -1
- package/lib/module/features/player/setup.js +3 -1
- package/lib/module/features/player/setup.js.map +1 -1
- package/lib/module/features/player/validateOptions.js +2 -0
- package/lib/module/features/player/validateOptions.js.map +1 -1
- package/lib/module/features/queue/activeTrack.js +14 -3
- package/lib/module/features/queue/activeTrack.js.map +1 -1
- package/lib/module/features/queue/queue.js +11 -4
- package/lib/module/features/queue/queue.js.map +1 -1
- package/lib/module/utils/LazyNativeEmitter.js +21 -10
- package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/module/utils/NativeUpdatedValue.js +31 -13
- package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/module/utils/getTrackIdentity.js +20 -0
- package/lib/module/utils/getTrackIdentity.js.map +1 -0
- package/lib/module/utils/useNativeValueRefreshedBy.js +29 -0
- package/lib/module/utils/useNativeValueRefreshedBy.js.map +1 -0
- package/lib/module/web/NativeAudioBrowser.js +32 -24
- package/lib/module/web/NativeAudioBrowser.js.map +1 -1
- package/lib/module/web/browser/BrowserManager.js +144 -87
- package/lib/module/web/browser/BrowserManager.js.map +1 -1
- package/lib/module/web/browser/FavoriteManager.js +31 -21
- package/lib/module/web/browser/FavoriteManager.js.map +1 -1
- package/lib/module/web/browser/assertBrowsePageShape.js +2 -2
- package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -1
- package/lib/module/web/browser/classifyTrackNavigation.js +9 -9
- package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -1
- package/lib/module/web/browser/sections.js +83 -0
- package/lib/module/web/browser/sections.js.map +1 -0
- package/lib/module/web/player/NowPlayingManager.js +4 -3
- package/lib/module/web/player/NowPlayingManager.js.map +1 -1
- package/lib/module/web/util/BrowserPathHelper.js +56 -11
- package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
- package/lib/typescript/src/AudioBrowser.d.ts +1 -0
- package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
- package/lib/typescript/src/features/browser-config.d.ts.map +1 -1
- package/lib/typescript/src/features/browser.d.ts +2 -2
- package/lib/typescript/src/features/favorites.d.ts +13 -10
- package/lib/typescript/src/features/favorites.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/controls.d.ts +4 -2
- package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/state.d.ts +2 -3
- package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +1 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -1
- package/lib/typescript/src/features/player/options.d.ts +21 -19
- package/lib/typescript/src/features/player/options.d.ts.map +1 -1
- package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
- package/lib/typescript/src/features/player/validateOptions.d.ts +1 -8
- package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/activeTrack.d.ts +8 -1
- package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/queue.d.ts +5 -2
- package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-native.d.ts +2 -2
- package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-nodes.d.ts +260 -127
- package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
- package/lib/typescript/src/types/browser.d.ts +33 -53
- package/lib/typescript/src/types/browser.d.ts.map +1 -1
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +16 -17
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +24 -19
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
- package/lib/typescript/src/utils/getTrackIdentity.d.ts +18 -0
- package/lib/typescript/src/utils/getTrackIdentity.d.ts.map +1 -0
- package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts +21 -0
- package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts.map +1 -0
- package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +0 -1
- package/nitrogen/generated/android/c++/{JFavoritesMatchMode.hpp → JArtworkRendering.hpp} +15 -15
- package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +4 -3
- package/nitrogen/generated/android/c++/JBrowseResult.hpp +10 -4
- package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
- package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +4 -3
- package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +4 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +4 -2
- 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
- package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +2 -5
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +9 -3
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +4 -2
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +21 -14
- package/nitrogen/generated/android/c++/JIOSOptions.hpp +9 -3
- package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +4 -3
- package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +16 -10
- package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +8 -2
- package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +6 -7
- package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +2 -5
- package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +9 -3
- package/nitrogen/generated/android/c++/JOptions.hpp +2 -5
- package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +4 -3
- package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
- package/nitrogen/generated/android/c++/JResolvedTrack.hpp +49 -51
- package/nitrogen/generated/android/c++/JSection.hpp +114 -0
- package/nitrogen/generated/android/c++/JSectionStyle.hpp +69 -0
- package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
- package/nitrogen/generated/android/c++/JTrack.hpp +19 -48
- package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +4 -2
- package/nitrogen/generated/android/c++/JTrackStyle.hpp +25 -18
- package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +10 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → ArtworkRendering.kt} +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayPlayingIndicatorLocation.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -18
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Section.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → SectionStyle.kt} +12 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StyleDisplay.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +6 -15
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +23 -5
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +104 -82
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +16 -10
- package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +16 -10
- package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
- package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
- package/nitrogen/generated/ios/swift/IOSOptions.swift +7 -2
- package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +5 -5
- package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +12 -1
- package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
- package/nitrogen/generated/ios/swift/ResolvedTrack.swift +54 -83
- package/nitrogen/generated/ios/swift/Section.swift +100 -0
- package/nitrogen/generated/ios/swift/SectionStyle.swift +64 -0
- package/nitrogen/generated/ios/swift/StyleDisplay.swift +40 -0
- package/nitrogen/generated/ios/swift/Track.swift +20 -73
- package/nitrogen/generated/ios/swift/TrackStyle.swift +28 -22
- package/nitrogen/generated/shared/c++/{FavoritesMatchMode.hpp → ArtworkRendering.hpp} +17 -17
- package/nitrogen/generated/shared/c++/CarPlayPlayingIndicatorLocation.hpp +76 -0
- package/nitrogen/generated/shared/c++/IOSOptions.hpp +9 -2
- package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +6 -6
- package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +9 -2
- package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
- package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +30 -38
- package/nitrogen/generated/shared/c++/Section.hpp +106 -0
- package/nitrogen/generated/shared/c++/SectionStyle.hpp +96 -0
- package/nitrogen/generated/shared/c++/StyleDisplay.hpp +76 -0
- package/nitrogen/generated/shared/c++/Track.hpp +15 -31
- package/nitrogen/generated/shared/c++/TrackStyle.hpp +50 -34
- package/package.json +3 -2
- package/src/AudioBrowser.ts +1 -0
- package/src/features/browser-config.test.ts +21 -6
- package/src/features/browser-config.ts +11 -8
- package/src/features/browser.test.ts +1 -1
- package/src/features/browser.ts +2 -2
- package/src/features/favorites.ts +13 -10
- package/src/features/hook-wiring.test.ts +63 -15
- package/src/features/playback/controls.ts +4 -2
- package/src/features/playback/state.ts +2 -3
- package/src/features/playback/trackPlaybackTime.ts +1 -1
- package/src/features/player/options.ts +27 -19
- package/src/features/player/setup.test.ts +12 -3
- package/src/features/player/setup.ts +3 -1
- package/src/features/player/validateOptions.ts +2 -0
- package/src/features/queue/activeTrack.ts +14 -3
- package/src/features/queue/queue.ts +11 -4
- package/src/nitro-stub-drift.test.ts +277 -0
- package/src/public-surface.test.ts +40 -0
- package/src/specs/audio-browser.nitro.ts +4 -3
- package/src/style-resolution.test.ts +139 -0
- package/src/types/browser-native.ts +2 -2
- package/src/types/browser-nodes.ts +268 -136
- package/src/types/browser.ts +34 -55
- package/src/utils/LazyNativeEmitter.ts +20 -10
- package/src/utils/NativeUpdatedValue.ts +29 -13
- package/src/utils/getTrackIdentity.ts +20 -0
- package/src/utils/useNativeValueRefreshedBy.ts +38 -0
- package/src/web/NativeAudioBrowser.ts +37 -22
- package/src/web/browser/BrowserManager.ts +176 -95
- package/src/web/browser/FavoriteManager.test.ts +84 -0
- package/src/web/browser/FavoriteManager.ts +28 -23
- package/src/web/browser/assertBrowsePageShape.test.ts +1 -1
- package/src/web/browser/assertBrowsePageShape.ts +5 -3
- package/src/web/browser/classifyTrackNavigation.test.ts +10 -8
- package/src/web/browser/classifyTrackNavigation.ts +9 -9
- package/src/web/browser/parseSearchResponse.test.ts +8 -2
- package/src/web/browser/sections.test.ts +91 -0
- package/src/web/browser/sections.ts +72 -0
- package/src/web/player/NowPlayingManager.ts +4 -3
- package/src/web/util/BrowserPathHelper.test.ts +52 -0
- package/src/web/util/BrowserPathHelper.ts +61 -13
- package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
- package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
- package/nitrogen/generated/android/c++/JImageRowItem.hpp +0 -105
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +0 -68
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
- package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
- package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
- package/nitrogen/generated/ios/swift/ImageRowItem.swift +0 -195
- package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
- package/nitrogen/generated/shared/c++/FavoriteConfig.hpp +0 -84
- package/nitrogen/generated/shared/c++/ImageRowItem.hpp +0 -129
|
@@ -176,15 +176,22 @@ suspend fun BrowserManager.resolveArtworkUrl(
|
|
|
176
176
|
)
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
// `{id}` token substitution
|
|
179
|
+
// `{id}` token substitution. A track without a non-blank id leaves the token LITERALLY in
|
|
180
|
+
// place — the request then visibly 404s, which is intended (self-describing error trail) —
|
|
181
|
+
// with a single warning logged below once the URL is built. Mirrors iOS substituteTrackId.
|
|
180
182
|
track.id
|
|
181
183
|
?.takeIf { it.isNotEmpty() }
|
|
182
184
|
?.let { transformedConfig = substituteTrackId(transformedConfig, it) }
|
|
185
|
+
val hasUnfilledIdToken = configContainsIdToken(transformedConfig)
|
|
183
186
|
|
|
184
187
|
val uri = RequestConfigBuilder.buildUrl(transformedConfig)
|
|
185
188
|
// If URI is empty, there's no valid artwork path
|
|
186
189
|
if (uri.isEmpty()) return null
|
|
187
190
|
|
|
191
|
+
if (hasUnfilledIdToken) {
|
|
192
|
+
Timber.w("nowPlayingArtwork: track has no id — '{id}' left unfilled in %s", uri)
|
|
193
|
+
}
|
|
194
|
+
|
|
188
195
|
ImageSource(
|
|
189
196
|
uri = uri,
|
|
190
197
|
method = transformedConfig.method,
|
|
@@ -265,15 +272,18 @@ private fun applyImageQueryParams(
|
|
|
265
272
|
return config.copy(query = (config.query ?: emptyMap()) + contextQuery)
|
|
266
273
|
}
|
|
267
274
|
|
|
275
|
+
/** The `{id}` template token — see [substituteTrackId] / [configContainsIdToken]. */
|
|
276
|
+
private const val ID_TOKEN = "{id}"
|
|
277
|
+
|
|
268
278
|
/**
|
|
269
279
|
* Replaces the `{id}` token with the track id in a request config's path, query values, and header
|
|
270
280
|
* values. Used so a `nowPlayingArtwork` like `{ path: "/artwork/{id}" }` resolves. Configs without
|
|
271
281
|
* the token are returned unchanged. Mirrors the iOS `substituteTrackId` helper.
|
|
272
282
|
*/
|
|
273
283
|
private fun substituteTrackId(config: RequestConfig, id: String): RequestConfig {
|
|
274
|
-
fun sub(s: String?): String? = s?.replace(
|
|
284
|
+
fun sub(s: String?): String? = s?.replace(ID_TOKEN, id)
|
|
275
285
|
fun subMap(m: Map<String, String>?): Map<String, String>? =
|
|
276
|
-
m?.mapValues { (_, value) -> value.replace(
|
|
286
|
+
m?.mapValues { (_, value) -> value.replace(ID_TOKEN, id) }
|
|
277
287
|
return config.copy(
|
|
278
288
|
path = sub(config.path),
|
|
279
289
|
headers = subMap(config.headers),
|
|
@@ -281,6 +291,16 @@ private fun substituteTrackId(config: RequestConfig, id: String): RequestConfig
|
|
|
281
291
|
)
|
|
282
292
|
}
|
|
283
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Whether the config still carries an unfilled `{id}` token in its path, query values, or header
|
|
296
|
+
* values (checked AFTER substitution, so it matches exactly when the track had no non-blank id and
|
|
297
|
+
* the config uses the token). Mirrors iOS `TrackIdTemplate.containsToken`.
|
|
298
|
+
*/
|
|
299
|
+
private fun configContainsIdToken(config: RequestConfig): Boolean =
|
|
300
|
+
config.path?.contains(ID_TOKEN) == true ||
|
|
301
|
+
config.query?.values?.any { it.contains(ID_TOKEN) } == true ||
|
|
302
|
+
config.headers?.values?.any { it.contains(ID_TOKEN) } == true
|
|
303
|
+
|
|
284
304
|
/** Builds a headers map, merging explicit headers with userAgent and contentType. */
|
|
285
305
|
private fun buildHeadersMap(
|
|
286
306
|
headers: Map<String, String>?,
|
|
@@ -1,30 +1,25 @@
|
|
|
1
1
|
package com.audiobrowser.browser
|
|
2
2
|
|
|
3
|
+
import com.audiobrowser.util.toArtworkRendering
|
|
4
|
+
import com.audiobrowser.util.toStyleDisplay
|
|
3
5
|
import com.margelo.nitro.audiobrowser.CarPlaySiriListButtonPosition
|
|
4
|
-
import com.margelo.nitro.audiobrowser.ImageRowItem
|
|
5
6
|
import com.margelo.nitro.audiobrowser.ResolvedTrack
|
|
7
|
+
import com.margelo.nitro.audiobrowser.Section
|
|
8
|
+
import com.margelo.nitro.audiobrowser.SectionStyle
|
|
6
9
|
import com.margelo.nitro.audiobrowser.Track
|
|
7
10
|
import com.margelo.nitro.audiobrowser.TrackRequest
|
|
8
11
|
import com.margelo.nitro.audiobrowser.TrackStyle
|
|
9
12
|
import kotlinx.serialization.Serializable
|
|
13
|
+
import kotlinx.serialization.json.JsonElement
|
|
14
|
+
import kotlinx.serialization.json.JsonObject
|
|
15
|
+
import kotlinx.serialization.json.JsonPrimitive
|
|
16
|
+
import kotlinx.serialization.json.booleanOrNull
|
|
10
17
|
|
|
11
18
|
/**
|
|
12
19
|
* JSON serializable models for parsing API responses. These will be converted to Nitro types after
|
|
13
|
-
* parsing.
|
|
20
|
+
* parsing. Legacy `groupTitle`/`imageRow` keys in payloads are simply unknown to these models and
|
|
21
|
+
* decode as ignored dead weight (ADR 0010).
|
|
14
22
|
*/
|
|
15
|
-
@Serializable
|
|
16
|
-
data class JsonImageRowItem(
|
|
17
|
-
val id: String? = null,
|
|
18
|
-
val url: String? = null,
|
|
19
|
-
val src: String? = null,
|
|
20
|
-
val artwork: String? = null,
|
|
21
|
-
val title: String,
|
|
22
|
-
val artist: String? = null,
|
|
23
|
-
val album: String? = null,
|
|
24
|
-
val albumUrl: String? = null,
|
|
25
|
-
val live: Boolean? = null,
|
|
26
|
-
val request: JsonTrackRequest? = null,
|
|
27
|
-
)
|
|
28
23
|
|
|
29
24
|
/** JSON model for a track's per-request HTTP override (identity/auth/signed-url). */
|
|
30
25
|
@Serializable
|
|
@@ -36,25 +31,35 @@ data class JsonTrackRequest(
|
|
|
36
31
|
fun toNitro(): TrackRequest = TrackRequest(userAgent, headers, query)
|
|
37
32
|
}
|
|
38
33
|
|
|
34
|
+
/** JSON model for a page section (ADR 0010). */
|
|
35
|
+
@Serializable
|
|
36
|
+
data class JsonSection(
|
|
37
|
+
val title: String? = null,
|
|
38
|
+
val subtitle: String? = null,
|
|
39
|
+
val style: JsonElement? = null,
|
|
40
|
+
val path: String? = null,
|
|
41
|
+
val children: List<JsonTrack>,
|
|
42
|
+
)
|
|
43
|
+
|
|
39
44
|
@Serializable
|
|
40
45
|
data class JsonResolvedTrack(
|
|
41
46
|
val id: String? = null,
|
|
42
|
-
val
|
|
47
|
+
val path: String,
|
|
43
48
|
val title: String,
|
|
44
49
|
val subtitle: String? = null,
|
|
45
50
|
val icon: String? = null,
|
|
46
51
|
val artwork: JsonArtwork? = null,
|
|
47
52
|
val artist: String? = null,
|
|
48
|
-
val
|
|
53
|
+
val albumPath: String? = null,
|
|
49
54
|
val album: String? = null,
|
|
50
55
|
val description: String? = null,
|
|
51
56
|
val genre: String? = null,
|
|
52
57
|
val duration: Double? = null,
|
|
58
|
+
val sections: List<JsonSection>? = null,
|
|
53
59
|
val children: List<JsonTrack>? = null,
|
|
54
60
|
val src: String? = null,
|
|
55
|
-
val style:
|
|
56
|
-
val
|
|
57
|
-
val groupTitle: String? = null,
|
|
61
|
+
val style: JsonElement? = null,
|
|
62
|
+
val disabled: Boolean? = null,
|
|
58
63
|
val live: Boolean? = null,
|
|
59
64
|
val carPlaySiriListButton: String? = null,
|
|
60
65
|
)
|
|
@@ -62,34 +67,59 @@ data class JsonResolvedTrack(
|
|
|
62
67
|
@Serializable
|
|
63
68
|
data class JsonTrack(
|
|
64
69
|
val id: String? = null,
|
|
65
|
-
val
|
|
70
|
+
val path: String? = null,
|
|
66
71
|
val title: String,
|
|
67
72
|
val subtitle: String? = null,
|
|
68
73
|
val icon: String? = null,
|
|
69
74
|
val artwork: JsonArtwork? = null,
|
|
70
75
|
val artist: String? = null,
|
|
71
|
-
val
|
|
76
|
+
val albumPath: String? = null,
|
|
72
77
|
val album: String? = null,
|
|
73
78
|
val description: String? = null,
|
|
74
79
|
val genre: String? = null,
|
|
75
80
|
val duration: Double? = null,
|
|
76
81
|
val src: String? = null,
|
|
77
82
|
val request: JsonTrackRequest? = null,
|
|
78
|
-
val style:
|
|
79
|
-
val
|
|
80
|
-
val groupTitle: String? = null,
|
|
83
|
+
val style: JsonElement? = null,
|
|
84
|
+
val disabled: Boolean? = null,
|
|
81
85
|
val live: Boolean? = null,
|
|
82
|
-
val imageRow: List<JsonImageRowItem>? = null,
|
|
83
86
|
)
|
|
84
87
|
|
|
85
|
-
/**
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Convert JSON models to Nitro types.
|
|
90
|
+
*
|
|
91
|
+
* `style` rides the models as a raw [JsonElement] and maps leniently here — tolerant decoding by
|
|
92
|
+
* design (ADR 0011): a stale value (the retired string vocabulary), a wrong-typed field, or an
|
|
93
|
+
* unknown enum value is "no declaration", never a parse failure. A typed field would fail the whole
|
|
94
|
+
* page, since this decoder is otherwise strict.
|
|
95
|
+
*/
|
|
96
|
+
private fun JsonObject.stringField(key: String): String? =
|
|
97
|
+
(get(key) as? JsonPrimitive)?.takeIf { it.isString }?.content
|
|
98
|
+
|
|
99
|
+
/** A strict JSON boolean — a quoted "false" is a wrong-typed field, i.e. no declaration. */
|
|
100
|
+
private fun JsonObject.booleanField(key: String): Boolean? =
|
|
101
|
+
(get(key) as? JsonPrimitive)?.takeIf { !it.isString }?.booleanOrNull
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* A block that resolves to no declarations at all — `{}` or all-unknown values — maps to null, so
|
|
105
|
+
* "no declaration" has one shape on every platform (iOS makes the same collapse).
|
|
106
|
+
*/
|
|
107
|
+
private fun JsonElement?.toTrackStyle(): TrackStyle? =
|
|
108
|
+
(this as? JsonObject)?.let { obj ->
|
|
109
|
+
val display = obj.stringField("display").toStyleDisplay()
|
|
110
|
+
val artworkRendering = obj.stringField("artworkRendering").toArtworkRendering()
|
|
111
|
+
if (display == null && artworkRendering == null) null
|
|
112
|
+
else TrackStyle(display = display, artworkRendering = artworkRendering)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private fun JsonElement?.toSectionStyle(): SectionStyle? =
|
|
116
|
+
(this as? JsonObject)?.let { obj ->
|
|
117
|
+
val display = obj.stringField("display").toStyleDisplay()
|
|
118
|
+
val artworkRendering = obj.stringField("artworkRendering").toArtworkRendering()
|
|
119
|
+
val gridWrap = obj.booleanField("gridWrap")
|
|
120
|
+
if (display == null && artworkRendering == null && gridWrap == null) null
|
|
121
|
+
else SectionStyle(display = display, artworkRendering = artworkRendering, gridWrap = gridWrap)
|
|
91
122
|
}
|
|
92
|
-
}
|
|
93
123
|
|
|
94
124
|
private fun String?.toCarPlaySiriListButtonPosition(): CarPlaySiriListButtonPosition? {
|
|
95
125
|
return when (this?.lowercase()) {
|
|
@@ -99,26 +129,21 @@ private fun String?.toCarPlaySiriListButtonPosition(): CarPlaySiriListButtonPosi
|
|
|
99
129
|
}
|
|
100
130
|
}
|
|
101
131
|
|
|
102
|
-
|
|
103
|
-
return
|
|
104
|
-
id = id,
|
|
105
|
-
url = url,
|
|
106
|
-
src = src,
|
|
107
|
-
artwork = artwork,
|
|
108
|
-
artworkSource = null,
|
|
132
|
+
fun JsonSection.toNitro(): Section {
|
|
133
|
+
return Section(
|
|
109
134
|
title = title,
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
request = request?.toNitro(),
|
|
135
|
+
subtitle = subtitle,
|
|
136
|
+
style = style.toSectionStyle(),
|
|
137
|
+
path = path,
|
|
138
|
+
children = children.map { it.toNitro() }.toTypedArray(),
|
|
115
139
|
)
|
|
116
140
|
}
|
|
117
141
|
|
|
118
142
|
fun JsonResolvedTrack.toNitro(): ResolvedTrack {
|
|
119
143
|
return ResolvedTrack(
|
|
120
144
|
id = id,
|
|
121
|
-
|
|
145
|
+
path = path,
|
|
146
|
+
sections = sections?.map { it.toNitro() }?.toTypedArray(),
|
|
122
147
|
children = children?.map { it.toNitro() }?.toTypedArray(),
|
|
123
148
|
carPlaySiriListButton = carPlaySiriListButton.toCarPlaySiriListButtonPosition(),
|
|
124
149
|
title = title,
|
|
@@ -127,45 +152,39 @@ fun JsonResolvedTrack.toNitro(): ResolvedTrack {
|
|
|
127
152
|
artworkSource = null,
|
|
128
153
|
// Containers carry no per-track media-request override (mirrors iOS JsonResolvedTrack).
|
|
129
154
|
request = null,
|
|
130
|
-
artworkCarPlayTinted = null,
|
|
131
155
|
artist = artist,
|
|
132
|
-
|
|
156
|
+
albumPath = albumPath,
|
|
133
157
|
album = album,
|
|
134
158
|
description = description,
|
|
135
159
|
genre = genre,
|
|
136
160
|
duration = duration,
|
|
137
161
|
src = src,
|
|
138
|
-
style = style.
|
|
139
|
-
|
|
162
|
+
style = style.toSectionStyle(),
|
|
163
|
+
disabled = disabled,
|
|
140
164
|
favorited = null,
|
|
141
|
-
groupTitle = groupTitle,
|
|
142
165
|
live = live,
|
|
143
|
-
imageRow = null,
|
|
144
166
|
)
|
|
145
167
|
}
|
|
146
168
|
|
|
147
169
|
fun JsonTrack.toNitro(): Track {
|
|
148
170
|
return Track(
|
|
149
171
|
id = id,
|
|
150
|
-
|
|
172
|
+
path = path,
|
|
151
173
|
title = title,
|
|
152
174
|
subtitle = subtitle,
|
|
153
175
|
artwork = artwork?.toNitro(),
|
|
154
176
|
artworkSource = null,
|
|
155
177
|
request = request?.toNitro(),
|
|
156
|
-
artworkCarPlayTinted = null,
|
|
157
178
|
artist = artist,
|
|
158
|
-
|
|
179
|
+
albumPath = albumPath,
|
|
159
180
|
album = album,
|
|
160
181
|
description = description,
|
|
161
182
|
genre = genre,
|
|
162
183
|
duration = duration,
|
|
163
184
|
src = src,
|
|
164
185
|
style = style.toTrackStyle(),
|
|
165
|
-
|
|
186
|
+
disabled = disabled,
|
|
166
187
|
favorited = null,
|
|
167
|
-
groupTitle = groupTitle,
|
|
168
188
|
live = live,
|
|
169
|
-
imageRow = imageRow?.map { it.toNitro() }?.toTypedArray(),
|
|
170
189
|
)
|
|
171
190
|
}
|
|
@@ -1,42 +1,84 @@
|
|
|
1
1
|
package com.audiobrowser.browser
|
|
2
2
|
|
|
3
|
-
import com.
|
|
3
|
+
import com.audiobrowser.extension.identity
|
|
4
|
+
import com.margelo.nitro.audiobrowser.ResolvedTrack
|
|
5
|
+
import com.margelo.nitro.audiobrowser.Section
|
|
4
6
|
import com.margelo.nitro.audiobrowser.Track
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Queue scope is the tapped section, not the whole page (ADR 0006): the playback context a listener
|
|
8
10
|
* expects is the list they tapped in — a page aggregating several sections must not leak
|
|
9
|
-
* next/previous across them.
|
|
11
|
+
* next/previous across them. Sections are structural (ADR 0010), so scoping is a lookup, not a
|
|
12
|
+
* groupTitle-run derivation.
|
|
10
13
|
*/
|
|
11
14
|
object SectionScope {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* The contiguous `groupTitle` run around the tapped child (items with no group title form runs
|
|
18
|
-
* of their own).
|
|
19
|
-
*/
|
|
20
|
-
data class Run(val tracks: List<Track>) : Section()
|
|
21
|
-
}
|
|
15
|
+
/**
|
|
16
|
+
* The section containing the tapped identity, plus the tapped child's offset within it — non-null
|
|
17
|
+
* only when the stamped flat index pinned the exact copy.
|
|
18
|
+
*/
|
|
19
|
+
data class Scoped(val section: Section, val tappedOffset: Int?)
|
|
22
20
|
|
|
23
21
|
/**
|
|
24
|
-
* The section of [
|
|
22
|
+
* The section of [sections] containing the playable [trackId] (a track identity — id when
|
|
23
|
+
* non-blank, else src), or null when not found.
|
|
24
|
+
*
|
|
25
|
+
* [tappedIndex] — the flat page position stamped into the contextual URL (children concatenated
|
|
26
|
+
* in section order) — is a tie-breaker, never an identifier: when the child at that position
|
|
27
|
+
* still carries the tapped identity, it pins which section (and which copy) was tapped; when it
|
|
28
|
+
* doesn't (the list shifted), resolution falls back to the first section containing the identity.
|
|
29
|
+
* A stale index can therefore never select a different track — at worst a different copy of the
|
|
30
|
+
* same one.
|
|
25
31
|
*/
|
|
26
|
-
fun
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
fun scoped(sections: List<Section>, trackId: String, tappedIndex: Int? = null): Scoped? {
|
|
33
|
+
if (tappedIndex != null && tappedIndex >= 0) {
|
|
34
|
+
var start = 0
|
|
35
|
+
for (section in sections) {
|
|
36
|
+
val offset = tappedIndex - start
|
|
37
|
+
if (offset < section.children.size) {
|
|
38
|
+
if (section.children[offset].identity == trackId) {
|
|
39
|
+
return Scoped(section, offset)
|
|
40
|
+
}
|
|
41
|
+
break
|
|
42
|
+
}
|
|
43
|
+
start += section.children.size
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (section in sections) {
|
|
47
|
+
if (section.children.any { it.identity == trackId }) {
|
|
48
|
+
return Scoped(section, null)
|
|
31
49
|
}
|
|
32
50
|
}
|
|
33
|
-
|
|
34
|
-
if (index < 0) return null
|
|
35
|
-
val group = children[index].groupTitle
|
|
36
|
-
var start = index
|
|
37
|
-
while (start > 0 && children[start - 1].groupTitle == group) start--
|
|
38
|
-
var end = index
|
|
39
|
-
while (end + 1 < children.size && children[end + 1].groupTitle == group) end++
|
|
40
|
-
return Section.Run(children.subList(start, end + 1).toList())
|
|
51
|
+
return null
|
|
41
52
|
}
|
|
42
53
|
}
|
|
54
|
+
|
|
55
|
+
// Page shape helpers (ADR 0010)
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* One untitled section — the canonical wrap of a flat track list (`children` authoring sugar,
|
|
59
|
+
* search results — ADR 0010).
|
|
60
|
+
*/
|
|
61
|
+
fun untitledSection(children: Array<Track>): Section =
|
|
62
|
+
Section(title = null, subtitle = null, style = null, path = null, children = children)
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The canonical sectioned shape: `sections` wins when present; plain `children` is authoring sugar
|
|
66
|
+
* for one untitled section.
|
|
67
|
+
*/
|
|
68
|
+
val ResolvedTrack.normalizedSections: List<Section>?
|
|
69
|
+
get() = sections?.toList() ?: children?.let { listOf(untitledSection(it)) }
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* [normalizedSections] with the page block folded in: each section's style resolves `section ??
|
|
73
|
+
* page` per property ([StyleResolver], ADR 0011), so everything downstream sees only effective
|
|
74
|
+
* section blocks — a page is a Track that is also the container of its sections.
|
|
75
|
+
*/
|
|
76
|
+
fun ResolvedTrack.styleResolvedSections(): List<Section>? =
|
|
77
|
+
normalizedSections?.map { it.copy(style = StyleResolver.sectionStyle(it.style, style)) }
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The page's children concatenated in section order — the flattening that defines contextual
|
|
81
|
+
* `__index` positions and the flat views (tabs, search) of a sectioned page.
|
|
82
|
+
*/
|
|
83
|
+
val ResolvedTrack.flattenedChildren: List<Track>?
|
|
84
|
+
get() = normalizedSections?.flatMap { it.children.asList() }
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
package com.audiobrowser.browser
|
|
2
|
+
|
|
3
|
+
import com.margelo.nitro.audiobrowser.SectionStyle
|
|
4
|
+
import com.margelo.nitro.audiobrowser.TrackStyle
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The one place style declarations resolve to effective values (ADR 0011).
|
|
8
|
+
*
|
|
9
|
+
* Two mechanisms, deliberately distinct:
|
|
10
|
+
* - **Inherited item properties** resolve `track ?? section ?? page`, per property — a track's own
|
|
11
|
+
* declaration wins over the blocks that contain it.
|
|
12
|
+
* - **Container properties and the positional `display`** resolve by scope override: `section ??
|
|
13
|
+
* page` — the page declares for its whole scope, a section overrides for its own children.
|
|
14
|
+
* `display` is never inherited item-to-container: each holder describes its own children, and a
|
|
15
|
+
* track's `display` is the promise for the page *it* opens, not its rendering inside this
|
|
16
|
+
* section.
|
|
17
|
+
*
|
|
18
|
+
* Every key of `SectionStyle` must be read by [sectionStyle] and every inherited key of
|
|
19
|
+
* `TrackStyle` by [trackStyle] — Nitro flattens the spec's `extends`, so
|
|
20
|
+
* `src/style-resolution.test.ts` enforces this completeness, not the type system.
|
|
21
|
+
*/
|
|
22
|
+
object StyleResolver {
|
|
23
|
+
/**
|
|
24
|
+
* Folds the page block into a section's block: every `SectionStyle` key resolves `section ??
|
|
25
|
+
* page`. The result is the section's effective block; item resolution then only needs `track ??
|
|
26
|
+
* section`.
|
|
27
|
+
*/
|
|
28
|
+
fun sectionStyle(section: SectionStyle?, page: SectionStyle?): SectionStyle =
|
|
29
|
+
SectionStyle(
|
|
30
|
+
display = section?.display ?: page?.display,
|
|
31
|
+
artworkRendering = section?.artworkRendering ?: page?.artworkRendering,
|
|
32
|
+
gridWrap = section?.gridWrap ?: page?.gridWrap,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolves a track's effective item properties against its (page-folded) section block.
|
|
37
|
+
*
|
|
38
|
+
* No Android renderer consumes the result yet — today's only inherited property
|
|
39
|
+
* (`artworkRendering`) is CarPlay-rendered — but the resolver ships on both platforms so the
|
|
40
|
+
* completeness test can hold every future inherited key to the same rule from day one.
|
|
41
|
+
*/
|
|
42
|
+
fun trackStyle(track: TrackStyle?, section: SectionStyle?): TrackStyle =
|
|
43
|
+
TrackStyle(
|
|
44
|
+
// Positional deny-list: `display` is never inherited onto an item — resolved null, so no
|
|
45
|
+
// renderer can mistake the handle's page promise for this item's own layout.
|
|
46
|
+
display = null,
|
|
47
|
+
artworkRendering = track?.artworkRendering ?: section?.artworkRendering,
|
|
48
|
+
)
|
|
49
|
+
}
|
|
@@ -2,6 +2,7 @@ package com.audiobrowser.extension
|
|
|
2
2
|
|
|
3
3
|
import com.margelo.nitro.audiobrowser.ResolvedTrack
|
|
4
4
|
import com.margelo.nitro.audiobrowser.Track
|
|
5
|
+
import com.margelo.nitro.audiobrowser.TrackStyle
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* The Track view of a ResolvedTrack: every display/playback field carried over, the browse-only
|
|
@@ -12,24 +13,25 @@ import com.margelo.nitro.audiobrowser.Track
|
|
|
12
13
|
fun ResolvedTrack.toTrack(): Track =
|
|
13
14
|
Track(
|
|
14
15
|
id = id,
|
|
15
|
-
|
|
16
|
+
path = path,
|
|
16
17
|
src = src,
|
|
17
18
|
artwork = artwork,
|
|
18
19
|
artworkSource = artworkSource,
|
|
19
20
|
request = request,
|
|
20
|
-
artworkCarPlayTinted = artworkCarPlayTinted,
|
|
21
21
|
title = title,
|
|
22
22
|
subtitle = subtitle,
|
|
23
23
|
artist = artist,
|
|
24
|
-
|
|
24
|
+
albumPath = albumPath,
|
|
25
25
|
album = album,
|
|
26
26
|
description = description,
|
|
27
27
|
genre = genre,
|
|
28
28
|
duration = duration,
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
// An explicit SectionStyle → TrackStyle projection: the page's container
|
|
30
|
+
// properties (gridWrap) have no meaning on a plain Track, so they drop —
|
|
31
|
+
// Nitro flattens the spec's `extends`, making this narrowing a hand-written
|
|
32
|
+
// field list rather than an upcast (ADR 0011).
|
|
33
|
+
style = style?.let { TrackStyle(display = it.display, artworkRendering = it.artworkRendering) },
|
|
34
|
+
disabled = disabled,
|
|
31
35
|
favorited = favorited,
|
|
32
|
-
groupTitle = groupTitle,
|
|
33
36
|
live = live,
|
|
34
|
-
imageRow = imageRow,
|
|
35
37
|
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
package com.audiobrowser.extension
|
|
2
|
+
|
|
3
|
+
import com.audiobrowser.util.BrowserPathHelper
|
|
4
|
+
import com.margelo.nitro.audiobrowser.Track
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The queue position of the tapped surface, for skip-in-place. Exact-surface match first: a
|
|
8
|
+
* contextual path carries the tapped page position (`__index`), so path equality pins the exact
|
|
9
|
+
* copy when the page holds the same identity more than once. The identity match remains for
|
|
10
|
+
* index-less paths (e.g. pre-stamp persisted state); an index-stamped path with no exact match
|
|
11
|
+
* returns -1, and the caller falls through to expansion, which re-scopes the queue to the tapped
|
|
12
|
+
* section (ADR 0009).
|
|
13
|
+
*/
|
|
14
|
+
fun Array<Track>.indexOfTappedTrack(contextualPath: String, trackId: String?): Int {
|
|
15
|
+
val index = indexOfFirst { it.path == contextualPath }
|
|
16
|
+
if (index >= 0 || BrowserPathHelper.extractIndex(contextualPath) != null || trackId == null) {
|
|
17
|
+
return index
|
|
18
|
+
}
|
|
19
|
+
return indexOfFirst { it.identity == trackId }
|
|
20
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
package com.audiobrowser.extension
|
|
2
|
+
|
|
3
|
+
import com.margelo.nitro.audiobrowser.Track
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A track's identity: the opaque `id` when present (non-blank), else the playable `src`. Two tracks
|
|
7
|
+
* refer to the same item iff their identities are equal. Browsable-only tracks (neither `id` nor
|
|
8
|
+
* `src`) have no identity — they are addressed by `path` instead.
|
|
9
|
+
*
|
|
10
|
+
* This is THE comparison rule for favorites matching, section scoping, skip-in-place, the car
|
|
11
|
+
* now-playing row indicator, and the contextual `__trackId` — see ADR 0008. Mirrors the TS
|
|
12
|
+
* `trackIdentity` helper.
|
|
13
|
+
*/
|
|
14
|
+
val Track.identity: String?
|
|
15
|
+
get() = id?.takeUnless { it.isBlank() } ?: src
|