react-native-audio-browser 0.2.0-next.12 → 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.
- package/README.md +10 -0
- package/android/build.gradle +20 -1
- package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +5 -1
- package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +8 -0
- package/android/src/main/java/com/audiobrowser/browser/InertStyleDiagnostic.kt +267 -0
- package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +27 -9
- package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +11 -0
- package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +11 -1
- package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +1 -1
- package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +17 -2
- package/android/src/main/java/com/audiobrowser/player/Player.kt +3 -0
- package/android/src/main/java/com/audiobrowser/player/ShuffleOrderLedBy.kt +30 -0
- package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +73 -0
- package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +8 -1
- package/android/src/test/java/com/audiobrowser/TestFixtures.kt +49 -3
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +3 -2
- package/android/src/test/java/com/audiobrowser/browser/InertStyleDiagnosticTest.kt +277 -0
- package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +56 -0
- package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +4 -4
- package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +77 -10
- package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +2 -2
- package/android/src/test/java/com/audiobrowser/player/ShuffleOrderLedByTest.kt +79 -0
- package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +8 -8
- package/ios/Browser/BrowserManager.swift +106 -45
- package/ios/Browser/InertStyleDiagnostic.swift +279 -0
- package/ios/Browser/JsonModels.swift +81 -10
- package/ios/Browser/SectionPresentation.swift +30 -0
- package/ios/Browser/StyleResolver.swift +11 -0
- package/ios/Browser/TabBarEntries.swift +7 -1
- package/ios/CarPlay/CarPlayController.swift +139 -35
- package/ios/CarPlay/CarPlayListItemFactory.swift +99 -27
- package/ios/Extension/UIApplication+ActiveScene.swift +27 -0
- package/ios/Http/HttpClient.swift +8 -2
- package/ios/HybridAudioBrowser.swift +54 -11
- package/ios/Model/NitroTypeStubs.swift +60 -0
- package/ios/Player/MediaIntentCriteria.swift +13 -5
- package/ios/Player/MediaLoader.swift +33 -9
- package/ios/Player/NowPlayingUpdater.swift +53 -5
- package/ios/Player/PlaybackCoordinator.swift +11 -4
- package/ios/Player/PlaybackTimer.swift +8 -2
- package/ios/Player/QueueManager.swift +36 -10
- package/ios/Player/ShuffleOrder.swift +23 -0
- package/ios/Player/SleepTimerHandling.swift +13 -0
- package/ios/RemoteCommand/RemoteCommandController.swift +53 -18
- package/ios/Tests/InertStyleDiagnosticTests.swift +200 -0
- package/ios/Tests/JsonModelsTests.swift +76 -1
- package/ios/Tests/QueueManagerTests.swift +72 -6
- package/ios/Tests/RemoteCommandControllerTests.swift +16 -0
- package/ios/Tests/SectionPresentationTests.swift +29 -0
- package/ios/Tests/StyleResolverTests.swift +60 -0
- package/ios/Tests/Support/TestTypes.swift +7 -0
- package/ios/TrackPlayer.swift +57 -34
- package/ios/Util/NetworkMonitor.swift +43 -26
- package/ios/Util/SFSymbolRenderer.swift +24 -2
- package/ios/Util/SVGProcessor.swift +9 -2
- package/lib/commonjs/features/playback/progress.js +2 -1
- package/lib/commonjs/features/playback/progress.js.map +1 -1
- package/lib/commonjs/features/playback/rate.js +8 -1
- package/lib/commonjs/features/playback/rate.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/QueueManager.js +14 -0
- package/lib/commonjs/web/TrackPlayer/QueueManager.js.map +1 -1
- package/lib/module/features/playback/progress.js +2 -1
- package/lib/module/features/playback/progress.js.map +1 -1
- package/lib/module/features/playback/rate.js +8 -1
- package/lib/module/features/playback/rate.js.map +1 -1
- package/lib/module/web/TrackPlayer/QueueManager.js +14 -0
- package/lib/module/web/TrackPlayer/QueueManager.js.map +1 -1
- package/lib/typescript/src/features/playback/progress.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/rate.d.ts +8 -1
- package/lib/typescript/src/features/playback/rate.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-nodes.d.ts +104 -2
- package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +4 -0
- package/nitrogen/generated/android/c++/JBrowseResult.hpp +6 -0
- package/nitrogen/generated/android/c++/JCardImage.hpp +58 -0
- package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +4 -0
- package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +4 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +4 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +4 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +4 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +4 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +4 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +4 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +6 -0
- package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +4 -0
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +4 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +6 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +4 -0
- package/nitrogen/generated/android/c++/JGridTile.hpp +61 -0
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +12 -0
- package/nitrogen/generated/android/c++/JImageShape.hpp +58 -0
- package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +4 -0
- package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +6 -0
- package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +6 -0
- package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +4 -0
- package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +4 -0
- package/nitrogen/generated/android/c++/JResolvedTrack.hpp +6 -0
- package/nitrogen/generated/android/c++/JSection.hpp +6 -0
- package/nitrogen/generated/android/c++/JSectionStyle.hpp +30 -3
- package/nitrogen/generated/android/c++/JTrack.hpp +4 -0
- package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +4 -0
- package/nitrogen/generated/android/c++/JTrackStyle.hpp +24 -3
- package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +6 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CardImage.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/GridTile.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageShape.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SectionStyle.kt +18 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +15 -3
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +54 -0
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +9 -0
- package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +9 -0
- package/nitrogen/generated/ios/swift/CardImage.swift +40 -0
- package/nitrogen/generated/ios/swift/GridTile.swift +44 -0
- package/nitrogen/generated/ios/swift/ImageShape.swift +40 -0
- package/nitrogen/generated/ios/swift/SectionStyle.swift +70 -1
- package/nitrogen/generated/ios/swift/TrackStyle.swift +59 -1
- package/nitrogen/generated/shared/c++/CardImage.hpp +76 -0
- package/nitrogen/generated/shared/c++/GridTile.hpp +80 -0
- package/nitrogen/generated/shared/c++/ImageShape.hpp +76 -0
- package/nitrogen/generated/shared/c++/SectionStyle.hpp +32 -2
- package/nitrogen/generated/shared/c++/TrackStyle.hpp +25 -2
- package/package.json +7 -2
- package/src/features/playback/progress.ts +2 -1
- package/src/features/playback/rate.ts +8 -1
- package/src/style-resolution.test.ts +72 -1
- package/src/types/browser-nodes.ts +112 -2
- package/src/web/TrackPlayer/QueueManager.test.ts +29 -0
- package/src/web/TrackPlayer/QueueManager.ts +14 -0
- package/website/guide/android-auto.md +114 -0
- package/website/guide/android-certificates.md +409 -0
- package/website/guide/artwork.md +255 -0
- package/website/guide/audio-output.md +135 -0
- package/website/guide/automotive.md +86 -0
- package/website/guide/basic-usage.md +151 -0
- package/website/guide/battery.md +115 -0
- package/website/guide/browser.md +581 -0
- package/website/guide/carplay.md +620 -0
- package/website/guide/configuration.md +173 -0
- package/website/guide/equalizer.md +231 -0
- package/website/guide/errors.md +310 -0
- package/website/guide/favorites.md +154 -0
- package/website/guide/gate.md +143 -0
- package/website/guide/getting-started.md +46 -0
- package/website/guide/hooks.md +238 -0
- package/website/guide/live-streams.md +125 -0
- package/website/guide/metadata.md +239 -0
- package/website/guide/migrating-from-track-player.md +244 -0
- package/website/guide/native-callback-fetch.md +127 -0
- package/website/guide/network.md +86 -0
- package/website/guide/now-playing.md +175 -0
- package/website/guide/playback.md +328 -0
- package/website/guide/queue.md +300 -0
- package/website/guide/remote-controls.md +256 -0
- package/website/guide/search.md +255 -0
- package/website/guide/sleep-timer.md +268 -0
- package/website/guide/track.md +246 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// GridTile.hpp
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#if __has_include(<NitroModules/NitroHash.hpp>)
|
|
11
|
+
#include <NitroModules/NitroHash.hpp>
|
|
12
|
+
#else
|
|
13
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
14
|
+
#endif
|
|
15
|
+
#if __has_include(<NitroModules/JSIConverter.hpp>)
|
|
16
|
+
#include <NitroModules/JSIConverter.hpp>
|
|
17
|
+
#else
|
|
18
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
19
|
+
#endif
|
|
20
|
+
#if __has_include(<NitroModules/NitroDefines.hpp>)
|
|
21
|
+
#include <NitroModules/NitroDefines.hpp>
|
|
22
|
+
#else
|
|
23
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
24
|
+
#endif
|
|
25
|
+
|
|
26
|
+
namespace margelo::nitro::audiobrowser {
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* An enum which can be represented as a JavaScript union (GridTile).
|
|
30
|
+
*/
|
|
31
|
+
enum class GridTile {
|
|
32
|
+
PLAIN SWIFT_NAME(plain) = 0,
|
|
33
|
+
CARD SWIFT_NAME(card) = 1,
|
|
34
|
+
CONDENSED SWIFT_NAME(condensed) = 2,
|
|
35
|
+
} CLOSED_ENUM;
|
|
36
|
+
|
|
37
|
+
} // namespace margelo::nitro::audiobrowser
|
|
38
|
+
|
|
39
|
+
namespace margelo::nitro {
|
|
40
|
+
|
|
41
|
+
// C++ GridTile <> JS GridTile (union)
|
|
42
|
+
template <>
|
|
43
|
+
struct JSIConverter<margelo::nitro::audiobrowser::GridTile> final {
|
|
44
|
+
static inline margelo::nitro::audiobrowser::GridTile fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
|
|
45
|
+
std::string unionValue = JSIConverter<std::string>::fromJSI(runtime, arg);
|
|
46
|
+
switch (hashString(unionValue.c_str(), unionValue.size())) {
|
|
47
|
+
case hashString("plain"): return margelo::nitro::audiobrowser::GridTile::PLAIN;
|
|
48
|
+
case hashString("card"): return margelo::nitro::audiobrowser::GridTile::CARD;
|
|
49
|
+
case hashString("condensed"): return margelo::nitro::audiobrowser::GridTile::CONDENSED;
|
|
50
|
+
default: [[unlikely]]
|
|
51
|
+
throw std::invalid_argument("Cannot convert \"" + unionValue + "\" to enum GridTile - invalid value!");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
static inline jsi::Value toJSI(jsi::Runtime& runtime, margelo::nitro::audiobrowser::GridTile arg) {
|
|
55
|
+
switch (arg) {
|
|
56
|
+
case margelo::nitro::audiobrowser::GridTile::PLAIN: return JSIConverter<std::string>::toJSI(runtime, "plain");
|
|
57
|
+
case margelo::nitro::audiobrowser::GridTile::CARD: return JSIConverter<std::string>::toJSI(runtime, "card");
|
|
58
|
+
case margelo::nitro::audiobrowser::GridTile::CONDENSED: return JSIConverter<std::string>::toJSI(runtime, "condensed");
|
|
59
|
+
default: [[unlikely]]
|
|
60
|
+
throw std::invalid_argument("Cannot convert GridTile to JS - invalid value: "
|
|
61
|
+
+ std::to_string(static_cast<int>(arg)) + "!");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
|
|
65
|
+
if (!value.isString()) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
std::string unionValue = JSIConverter<std::string>::fromJSI(runtime, value);
|
|
69
|
+
switch (hashString(unionValue.c_str(), unionValue.size())) {
|
|
70
|
+
case hashString("plain"):
|
|
71
|
+
case hashString("card"):
|
|
72
|
+
case hashString("condensed"):
|
|
73
|
+
return true;
|
|
74
|
+
default:
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
} // namespace margelo::nitro
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// ImageShape.hpp
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#if __has_include(<NitroModules/NitroHash.hpp>)
|
|
11
|
+
#include <NitroModules/NitroHash.hpp>
|
|
12
|
+
#else
|
|
13
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
14
|
+
#endif
|
|
15
|
+
#if __has_include(<NitroModules/JSIConverter.hpp>)
|
|
16
|
+
#include <NitroModules/JSIConverter.hpp>
|
|
17
|
+
#else
|
|
18
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
19
|
+
#endif
|
|
20
|
+
#if __has_include(<NitroModules/NitroDefines.hpp>)
|
|
21
|
+
#include <NitroModules/NitroDefines.hpp>
|
|
22
|
+
#else
|
|
23
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
24
|
+
#endif
|
|
25
|
+
|
|
26
|
+
namespace margelo::nitro::audiobrowser {
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* An enum which can be represented as a JavaScript union (ImageShape).
|
|
30
|
+
*/
|
|
31
|
+
enum class ImageShape {
|
|
32
|
+
CIRCULAR SWIFT_NAME(circular) = 0,
|
|
33
|
+
ROUNDED_RECTANGLE SWIFT_NAME(roundedRectangle) = 1,
|
|
34
|
+
} CLOSED_ENUM;
|
|
35
|
+
|
|
36
|
+
} // namespace margelo::nitro::audiobrowser
|
|
37
|
+
|
|
38
|
+
namespace margelo::nitro {
|
|
39
|
+
|
|
40
|
+
// C++ ImageShape <> JS ImageShape (union)
|
|
41
|
+
template <>
|
|
42
|
+
struct JSIConverter<margelo::nitro::audiobrowser::ImageShape> final {
|
|
43
|
+
static inline margelo::nitro::audiobrowser::ImageShape fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
|
|
44
|
+
std::string unionValue = JSIConverter<std::string>::fromJSI(runtime, arg);
|
|
45
|
+
switch (hashString(unionValue.c_str(), unionValue.size())) {
|
|
46
|
+
case hashString("circular"): return margelo::nitro::audiobrowser::ImageShape::CIRCULAR;
|
|
47
|
+
case hashString("rounded-rectangle"): return margelo::nitro::audiobrowser::ImageShape::ROUNDED_RECTANGLE;
|
|
48
|
+
default: [[unlikely]]
|
|
49
|
+
throw std::invalid_argument("Cannot convert \"" + unionValue + "\" to enum ImageShape - invalid value!");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
static inline jsi::Value toJSI(jsi::Runtime& runtime, margelo::nitro::audiobrowser::ImageShape arg) {
|
|
53
|
+
switch (arg) {
|
|
54
|
+
case margelo::nitro::audiobrowser::ImageShape::CIRCULAR: return JSIConverter<std::string>::toJSI(runtime, "circular");
|
|
55
|
+
case margelo::nitro::audiobrowser::ImageShape::ROUNDED_RECTANGLE: return JSIConverter<std::string>::toJSI(runtime, "rounded-rectangle");
|
|
56
|
+
default: [[unlikely]]
|
|
57
|
+
throw std::invalid_argument("Cannot convert ImageShape to JS - invalid value: "
|
|
58
|
+
+ std::to_string(static_cast<int>(arg)) + "!");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
|
|
62
|
+
if (!value.isString()) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
std::string unionValue = JSIConverter<std::string>::fromJSI(runtime, value);
|
|
66
|
+
switch (hashString(unionValue.c_str(), unionValue.size())) {
|
|
67
|
+
case hashString("circular"):
|
|
68
|
+
case hashString("rounded-rectangle"):
|
|
69
|
+
return true;
|
|
70
|
+
default:
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
} // namespace margelo::nitro
|
|
@@ -28,14 +28,24 @@
|
|
|
28
28
|
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
29
29
|
#endif
|
|
30
30
|
|
|
31
|
+
// Forward declaration of `GridTile` to properly resolve imports.
|
|
32
|
+
namespace margelo::nitro::audiobrowser { enum class GridTile; }
|
|
31
33
|
// Forward declaration of `StyleDisplay` to properly resolve imports.
|
|
32
34
|
namespace margelo::nitro::audiobrowser { enum class StyleDisplay; }
|
|
33
35
|
// Forward declaration of `ArtworkRendering` to properly resolve imports.
|
|
34
36
|
namespace margelo::nitro::audiobrowser { enum class ArtworkRendering; }
|
|
37
|
+
// Forward declaration of `ImageShape` to properly resolve imports.
|
|
38
|
+
namespace margelo::nitro::audiobrowser { enum class ImageShape; }
|
|
39
|
+
// Forward declaration of `CardImage` to properly resolve imports.
|
|
40
|
+
namespace margelo::nitro::audiobrowser { enum class CardImage; }
|
|
35
41
|
|
|
36
42
|
#include <optional>
|
|
43
|
+
#include "GridTile.hpp"
|
|
37
44
|
#include "StyleDisplay.hpp"
|
|
38
45
|
#include "ArtworkRendering.hpp"
|
|
46
|
+
#include "ImageShape.hpp"
|
|
47
|
+
#include <string>
|
|
48
|
+
#include "CardImage.hpp"
|
|
39
49
|
|
|
40
50
|
namespace margelo::nitro::audiobrowser {
|
|
41
51
|
|
|
@@ -45,12 +55,17 @@ namespace margelo::nitro::audiobrowser {
|
|
|
45
55
|
struct SectionStyle final {
|
|
46
56
|
public:
|
|
47
57
|
std::optional<bool> gridWrap SWIFT_PRIVATE;
|
|
58
|
+
std::optional<GridTile> gridTile SWIFT_PRIVATE;
|
|
48
59
|
std::optional<StyleDisplay> display SWIFT_PRIVATE;
|
|
49
60
|
std::optional<ArtworkRendering> artworkRendering SWIFT_PRIVATE;
|
|
61
|
+
std::optional<ImageShape> imageShape SWIFT_PRIVATE;
|
|
62
|
+
std::optional<std::string> accessorySymbol SWIFT_PRIVATE;
|
|
63
|
+
std::optional<std::string> cardTint SWIFT_PRIVATE;
|
|
64
|
+
std::optional<CardImage> cardImage SWIFT_PRIVATE;
|
|
50
65
|
|
|
51
66
|
public:
|
|
52
67
|
SectionStyle() = default;
|
|
53
|
-
explicit SectionStyle(std::optional<bool> gridWrap, std::optional<StyleDisplay> display, std::optional<ArtworkRendering> artworkRendering): gridWrap(gridWrap), display(display), artworkRendering(artworkRendering) {}
|
|
68
|
+
explicit SectionStyle(std::optional<bool> gridWrap, std::optional<GridTile> gridTile, std::optional<StyleDisplay> display, std::optional<ArtworkRendering> artworkRendering, std::optional<ImageShape> imageShape, std::optional<std::string> accessorySymbol, std::optional<std::string> cardTint, std::optional<CardImage> cardImage): gridWrap(gridWrap), gridTile(gridTile), display(display), artworkRendering(artworkRendering), imageShape(imageShape), accessorySymbol(accessorySymbol), cardTint(cardTint), cardImage(cardImage) {}
|
|
54
69
|
|
|
55
70
|
public:
|
|
56
71
|
friend bool operator==(const SectionStyle& lhs, const SectionStyle& rhs) = default;
|
|
@@ -67,15 +82,25 @@ namespace margelo::nitro {
|
|
|
67
82
|
jsi::Object obj = arg.asObject(runtime);
|
|
68
83
|
return margelo::nitro::audiobrowser::SectionStyle(
|
|
69
84
|
JSIConverter<std::optional<bool>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "gridWrap"))),
|
|
85
|
+
JSIConverter<std::optional<margelo::nitro::audiobrowser::GridTile>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "gridTile"))),
|
|
70
86
|
JSIConverter<std::optional<margelo::nitro::audiobrowser::StyleDisplay>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "display"))),
|
|
71
|
-
JSIConverter<std::optional<margelo::nitro::audiobrowser::ArtworkRendering>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "artworkRendering")))
|
|
87
|
+
JSIConverter<std::optional<margelo::nitro::audiobrowser::ArtworkRendering>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "artworkRendering"))),
|
|
88
|
+
JSIConverter<std::optional<margelo::nitro::audiobrowser::ImageShape>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "imageShape"))),
|
|
89
|
+
JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "accessorySymbol"))),
|
|
90
|
+
JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "cardTint"))),
|
|
91
|
+
JSIConverter<std::optional<margelo::nitro::audiobrowser::CardImage>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "cardImage")))
|
|
72
92
|
);
|
|
73
93
|
}
|
|
74
94
|
static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::audiobrowser::SectionStyle& arg) {
|
|
75
95
|
jsi::Object obj(runtime);
|
|
76
96
|
obj.setProperty(runtime, PropNameIDCache::get(runtime, "gridWrap"), JSIConverter<std::optional<bool>>::toJSI(runtime, arg.gridWrap));
|
|
97
|
+
obj.setProperty(runtime, PropNameIDCache::get(runtime, "gridTile"), JSIConverter<std::optional<margelo::nitro::audiobrowser::GridTile>>::toJSI(runtime, arg.gridTile));
|
|
77
98
|
obj.setProperty(runtime, PropNameIDCache::get(runtime, "display"), JSIConverter<std::optional<margelo::nitro::audiobrowser::StyleDisplay>>::toJSI(runtime, arg.display));
|
|
78
99
|
obj.setProperty(runtime, PropNameIDCache::get(runtime, "artworkRendering"), JSIConverter<std::optional<margelo::nitro::audiobrowser::ArtworkRendering>>::toJSI(runtime, arg.artworkRendering));
|
|
100
|
+
obj.setProperty(runtime, PropNameIDCache::get(runtime, "imageShape"), JSIConverter<std::optional<margelo::nitro::audiobrowser::ImageShape>>::toJSI(runtime, arg.imageShape));
|
|
101
|
+
obj.setProperty(runtime, PropNameIDCache::get(runtime, "accessorySymbol"), JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.accessorySymbol));
|
|
102
|
+
obj.setProperty(runtime, PropNameIDCache::get(runtime, "cardTint"), JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.cardTint));
|
|
103
|
+
obj.setProperty(runtime, PropNameIDCache::get(runtime, "cardImage"), JSIConverter<std::optional<margelo::nitro::audiobrowser::CardImage>>::toJSI(runtime, arg.cardImage));
|
|
79
104
|
return obj;
|
|
80
105
|
}
|
|
81
106
|
static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
|
|
@@ -87,8 +112,13 @@ namespace margelo::nitro {
|
|
|
87
112
|
return false;
|
|
88
113
|
}
|
|
89
114
|
if (!JSIConverter<std::optional<bool>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "gridWrap")))) return false;
|
|
115
|
+
if (!JSIConverter<std::optional<margelo::nitro::audiobrowser::GridTile>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "gridTile")))) return false;
|
|
90
116
|
if (!JSIConverter<std::optional<margelo::nitro::audiobrowser::StyleDisplay>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "display")))) return false;
|
|
91
117
|
if (!JSIConverter<std::optional<margelo::nitro::audiobrowser::ArtworkRendering>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "artworkRendering")))) return false;
|
|
118
|
+
if (!JSIConverter<std::optional<margelo::nitro::audiobrowser::ImageShape>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "imageShape")))) return false;
|
|
119
|
+
if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "accessorySymbol")))) return false;
|
|
120
|
+
if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "cardTint")))) return false;
|
|
121
|
+
if (!JSIConverter<std::optional<margelo::nitro::audiobrowser::CardImage>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "cardImage")))) return false;
|
|
92
122
|
return true;
|
|
93
123
|
}
|
|
94
124
|
};
|
|
@@ -32,10 +32,17 @@
|
|
|
32
32
|
namespace margelo::nitro::audiobrowser { enum class StyleDisplay; }
|
|
33
33
|
// Forward declaration of `ArtworkRendering` to properly resolve imports.
|
|
34
34
|
namespace margelo::nitro::audiobrowser { enum class ArtworkRendering; }
|
|
35
|
+
// Forward declaration of `ImageShape` to properly resolve imports.
|
|
36
|
+
namespace margelo::nitro::audiobrowser { enum class ImageShape; }
|
|
37
|
+
// Forward declaration of `CardImage` to properly resolve imports.
|
|
38
|
+
namespace margelo::nitro::audiobrowser { enum class CardImage; }
|
|
35
39
|
|
|
36
40
|
#include "StyleDisplay.hpp"
|
|
37
41
|
#include <optional>
|
|
38
42
|
#include "ArtworkRendering.hpp"
|
|
43
|
+
#include "ImageShape.hpp"
|
|
44
|
+
#include <string>
|
|
45
|
+
#include "CardImage.hpp"
|
|
39
46
|
|
|
40
47
|
namespace margelo::nitro::audiobrowser {
|
|
41
48
|
|
|
@@ -46,10 +53,14 @@ namespace margelo::nitro::audiobrowser {
|
|
|
46
53
|
public:
|
|
47
54
|
std::optional<StyleDisplay> display SWIFT_PRIVATE;
|
|
48
55
|
std::optional<ArtworkRendering> artworkRendering SWIFT_PRIVATE;
|
|
56
|
+
std::optional<ImageShape> imageShape SWIFT_PRIVATE;
|
|
57
|
+
std::optional<std::string> accessorySymbol SWIFT_PRIVATE;
|
|
58
|
+
std::optional<std::string> cardTint SWIFT_PRIVATE;
|
|
59
|
+
std::optional<CardImage> cardImage SWIFT_PRIVATE;
|
|
49
60
|
|
|
50
61
|
public:
|
|
51
62
|
TrackStyle() = default;
|
|
52
|
-
explicit TrackStyle(std::optional<StyleDisplay> display, std::optional<ArtworkRendering> artworkRendering): display(display), artworkRendering(artworkRendering) {}
|
|
63
|
+
explicit TrackStyle(std::optional<StyleDisplay> display, std::optional<ArtworkRendering> artworkRendering, std::optional<ImageShape> imageShape, std::optional<std::string> accessorySymbol, std::optional<std::string> cardTint, std::optional<CardImage> cardImage): display(display), artworkRendering(artworkRendering), imageShape(imageShape), accessorySymbol(accessorySymbol), cardTint(cardTint), cardImage(cardImage) {}
|
|
53
64
|
|
|
54
65
|
public:
|
|
55
66
|
friend bool operator==(const TrackStyle& lhs, const TrackStyle& rhs) = default;
|
|
@@ -66,13 +77,21 @@ namespace margelo::nitro {
|
|
|
66
77
|
jsi::Object obj = arg.asObject(runtime);
|
|
67
78
|
return margelo::nitro::audiobrowser::TrackStyle(
|
|
68
79
|
JSIConverter<std::optional<margelo::nitro::audiobrowser::StyleDisplay>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "display"))),
|
|
69
|
-
JSIConverter<std::optional<margelo::nitro::audiobrowser::ArtworkRendering>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "artworkRendering")))
|
|
80
|
+
JSIConverter<std::optional<margelo::nitro::audiobrowser::ArtworkRendering>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "artworkRendering"))),
|
|
81
|
+
JSIConverter<std::optional<margelo::nitro::audiobrowser::ImageShape>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "imageShape"))),
|
|
82
|
+
JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "accessorySymbol"))),
|
|
83
|
+
JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "cardTint"))),
|
|
84
|
+
JSIConverter<std::optional<margelo::nitro::audiobrowser::CardImage>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "cardImage")))
|
|
70
85
|
);
|
|
71
86
|
}
|
|
72
87
|
static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::audiobrowser::TrackStyle& arg) {
|
|
73
88
|
jsi::Object obj(runtime);
|
|
74
89
|
obj.setProperty(runtime, PropNameIDCache::get(runtime, "display"), JSIConverter<std::optional<margelo::nitro::audiobrowser::StyleDisplay>>::toJSI(runtime, arg.display));
|
|
75
90
|
obj.setProperty(runtime, PropNameIDCache::get(runtime, "artworkRendering"), JSIConverter<std::optional<margelo::nitro::audiobrowser::ArtworkRendering>>::toJSI(runtime, arg.artworkRendering));
|
|
91
|
+
obj.setProperty(runtime, PropNameIDCache::get(runtime, "imageShape"), JSIConverter<std::optional<margelo::nitro::audiobrowser::ImageShape>>::toJSI(runtime, arg.imageShape));
|
|
92
|
+
obj.setProperty(runtime, PropNameIDCache::get(runtime, "accessorySymbol"), JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.accessorySymbol));
|
|
93
|
+
obj.setProperty(runtime, PropNameIDCache::get(runtime, "cardTint"), JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.cardTint));
|
|
94
|
+
obj.setProperty(runtime, PropNameIDCache::get(runtime, "cardImage"), JSIConverter<std::optional<margelo::nitro::audiobrowser::CardImage>>::toJSI(runtime, arg.cardImage));
|
|
76
95
|
return obj;
|
|
77
96
|
}
|
|
78
97
|
static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
|
|
@@ -85,6 +104,10 @@ namespace margelo::nitro {
|
|
|
85
104
|
}
|
|
86
105
|
if (!JSIConverter<std::optional<margelo::nitro::audiobrowser::StyleDisplay>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "display")))) return false;
|
|
87
106
|
if (!JSIConverter<std::optional<margelo::nitro::audiobrowser::ArtworkRendering>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "artworkRendering")))) return false;
|
|
107
|
+
if (!JSIConverter<std::optional<margelo::nitro::audiobrowser::ImageShape>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "imageShape")))) return false;
|
|
108
|
+
if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "accessorySymbol")))) return false;
|
|
109
|
+
if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "cardTint")))) return false;
|
|
110
|
+
if (!JSIConverter<std::optional<margelo::nitro::audiobrowser::CardImage>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "cardImage")))) return false;
|
|
88
111
|
return true;
|
|
89
112
|
}
|
|
90
113
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-audio-browser",
|
|
3
|
-
"version": "0.2.0-next.
|
|
3
|
+
"version": "0.2.0-next.13",
|
|
4
4
|
"description": "react-native-audio-browser is a react native package built with Nitro",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"ios/**/*.mm",
|
|
32
32
|
"ios/**/*.cpp",
|
|
33
33
|
"ios/**/*.swift",
|
|
34
|
+
"website/guide",
|
|
34
35
|
"*.podspec",
|
|
35
36
|
"README.md"
|
|
36
37
|
],
|
|
@@ -46,23 +47,27 @@
|
|
|
46
47
|
"scripts": {
|
|
47
48
|
"android:format": "yarn workspace example-native android:format",
|
|
48
49
|
"android:format:check": "yarn workspace example-native android:format:check",
|
|
50
|
+
"android:gradle-stop": "yarn workspace example-native android:gradle-stop",
|
|
49
51
|
"android:ide": "yarn workspace example-native android:ide",
|
|
50
52
|
"android:logs": "react-native log-android",
|
|
51
53
|
"android:test": "yarn workspace example-native android:test",
|
|
54
|
+
"android:types": "yarn workspace example-native android:types",
|
|
52
55
|
"build": "yarn prepare",
|
|
53
56
|
"clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
|
|
54
57
|
"codegen": "node codegen/index.js --logLevel=\"debug\" && node post-script.cjs && node codegen/strip-struct-equality.cjs && yarn build",
|
|
55
58
|
"prepare": "bob build && yarn hooks:install",
|
|
56
59
|
"hooks:install": "git config core.hooksPath .githooks || exit 0",
|
|
57
60
|
"example": "yarn workspace example-native",
|
|
61
|
+
"ci:android": "yarn android:format:check && yarn android:types && yarn android:test",
|
|
58
62
|
"ci:format": "oxfmt --check --ignore-path .gitignore --ignore-path .oxfmtignore .",
|
|
59
63
|
"ci:format:ios": "swiftformat --lint ios",
|
|
64
|
+
"ci:ios": "yarn ci:format:ios && yarn ios:test",
|
|
60
65
|
"ci:lint": "oxlint --type-aware src",
|
|
61
66
|
"format": "oxfmt --write --ignore-path .gitignore --ignore-path .oxfmtignore .",
|
|
62
67
|
"ios:format": "swiftformat ios",
|
|
63
68
|
"ios:ide": "yarn workspace example-native ios:ide",
|
|
64
69
|
"ios:rebuild": "yarn workspace example-native ios:rebuild",
|
|
65
|
-
"ios:test": "swift test",
|
|
70
|
+
"ios:test": "swift test --disable-sandbox",
|
|
66
71
|
"lint": "oxlint --fix",
|
|
67
72
|
"release": "semantic-release",
|
|
68
73
|
"test": "vitest run",
|
|
@@ -118,7 +118,8 @@ export function usePolledProgress(updateInterval = 1000): Progress {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
const stop = () => {
|
|
121
|
-
clearTimeout(
|
|
121
|
+
// Guarded: React Native types clearTimeout(handle: number) — no undefined.
|
|
122
|
+
if (timer !== undefined) clearTimeout(timer)
|
|
122
123
|
timer = undefined
|
|
123
124
|
unsubscribeState?.()
|
|
124
125
|
unsubscribeState = undefined
|
|
@@ -14,8 +14,15 @@ export function getRate(): number {
|
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Sets the playback rate.
|
|
17
|
+
*
|
|
18
|
+
* The rate is a speed multiplier, not a transport control — it is independent
|
|
19
|
+
* of play/pause, and setting it while paused never starts playback. Use
|
|
20
|
+
* {@link play} / {@link pause} for that.
|
|
21
|
+
*
|
|
17
22
|
* @param rate - The playback rate to change to, where 0.5 would be half speed,
|
|
18
|
-
* 1 would be regular speed, 2 would be double speed etc.
|
|
23
|
+
* 1 would be regular speed, 2 would be double speed etc. Must be greater than
|
|
24
|
+
* zero; platforms disagree on what a non-positive rate does (iOS ignores it,
|
|
25
|
+
* Android throws, web stalls the element), so treat it as unsupported.
|
|
19
26
|
*/
|
|
20
27
|
export function setRate(rate: number): void {
|
|
21
28
|
nativeBrowser.setRate(rate)
|
|
@@ -3,7 +3,12 @@ import { join } from 'node:path'
|
|
|
3
3
|
import { describe, expect, it } from 'vitest'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Completeness guards for the style declaration block (ADR 0011) — the checks
|
|
7
|
+
* Nitro's `extends`-flattening took out of the type system, in two parts:
|
|
8
|
+
* every key must be *resolved* by each platform's `StyleResolver`, and every
|
|
9
|
+
* key must be *accounted for* by each platform's `InertStyleDiagnostic`.
|
|
10
|
+
*
|
|
11
|
+
* Part one — inheritance completeness.
|
|
7
12
|
*
|
|
8
13
|
* `SectionStyle extends TrackStyle` in the spec, but Nitro flattens `extends`
|
|
9
14
|
* into independent structs on every platform — so nothing in any type system
|
|
@@ -137,3 +142,69 @@ for (const resolver of RESOLVERS) {
|
|
|
137
142
|
})
|
|
138
143
|
})
|
|
139
144
|
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Part two — diagnostic completeness.
|
|
148
|
+
*
|
|
149
|
+
* The block model traded compile-time invalid-combination errors for
|
|
150
|
+
* inertness, and the dev-mode `InertStyleDiagnostic` is the recovery. It is
|
|
151
|
+
* exposed to the same flattening problem as the resolvers, one level up: a
|
|
152
|
+
* key added to the spec has no type-system tie to the diagnostic's rule set,
|
|
153
|
+
* so a property could ship whose inert combinations nobody is warned about.
|
|
154
|
+
*
|
|
155
|
+
* Each platform therefore dispositions the whole vocabulary — every key is a
|
|
156
|
+
* `Property` with an inertness rule, `alwaysRenderable`, or `positional` —
|
|
157
|
+
* and this test reads those three declarations and asserts set equality with
|
|
158
|
+
* the spec, both directions: a new key must be dispositioned, and a key
|
|
159
|
+
* deleted from the spec must not linger in the rule set.
|
|
160
|
+
*/
|
|
161
|
+
const DIAGNOSTICS = [
|
|
162
|
+
{
|
|
163
|
+
file: 'ios/Browser/InertStyleDiagnostic.swift',
|
|
164
|
+
// `case gridWrap`, up to the first member after the case list.
|
|
165
|
+
ruled: (source: string) =>
|
|
166
|
+
keysIn(
|
|
167
|
+
source,
|
|
168
|
+
/enum Property[\s\S]*?var isItemProperty/,
|
|
169
|
+
/^\s*case (\w+)$/gm
|
|
170
|
+
)
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
file: 'android/src/main/java/com/audiobrowser/browser/InertStyleDiagnostic.kt',
|
|
174
|
+
// `GRID_WRAP("gridWrap", …)`, up to the `;` that ends the entry list.
|
|
175
|
+
ruled: (source: string) =>
|
|
176
|
+
keysIn(source, /enum class Property[\s\S]*?;/, /\w+\("(\w+)"/g)
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
/** The capture group of every `pattern` match within the first `region`. */
|
|
181
|
+
function keysIn(source: string, region: RegExp, pattern: RegExp): string[] {
|
|
182
|
+
const found = region.exec(source)
|
|
183
|
+
expect(found, `region ${region} not found`).not.toBeNull()
|
|
184
|
+
return [...found![0].matchAll(pattern)].flatMap((match) =>
|
|
185
|
+
match[1] === undefined ? [] : [match[1]]
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
for (const diagnostic of DIAGNOSTICS) {
|
|
190
|
+
describe(diagnostic.file, () => {
|
|
191
|
+
// Comment-free, so prose naming a key doesn't pass for dispositioning it.
|
|
192
|
+
const source = stripComments(
|
|
193
|
+
readFileSync(join(ROOT, diagnostic.file), 'utf8')
|
|
194
|
+
)
|
|
195
|
+
// The two lists are spelled `alwaysRenderable`/`positional` in Swift and
|
|
196
|
+
// SCREAMING_CASE in Kotlin; both hold bare string literals.
|
|
197
|
+
const listed = [
|
|
198
|
+
...source.matchAll(
|
|
199
|
+
/(?:alwaysRenderable|ALWAYS_RENDERABLE|positional|POSITIONAL)\s*=[^\n]*/gi
|
|
200
|
+
)
|
|
201
|
+
].flatMap((match) =>
|
|
202
|
+
[...match[0].matchAll(/"(\w+)"/g)].map((key) => key[1])
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
it('dispositions every key of the block, and only those', () => {
|
|
206
|
+
const dispositioned = [...diagnostic.ruled(source), ...listed]
|
|
207
|
+
expect(new Set(dispositioned)).toEqual(new Set(sectionKeys))
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
}
|
|
@@ -25,6 +25,50 @@ export type StyleDisplay = 'list' | 'grid'
|
|
|
25
25
|
*/
|
|
26
26
|
export type ArtworkRendering = 'original' | 'stencil'
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* The shape a grid tile's artwork is cropped to.
|
|
30
|
+
*
|
|
31
|
+
* - `'rounded-rectangle'` (default) — the standard tile crop.
|
|
32
|
+
* - `'circular'` — the standard presentation for *people* (artists, hosts,
|
|
33
|
+
* DJs). Shape encodes the kind of item, so mixed grids legitimately
|
|
34
|
+
* render circular artists next to rounded albums.
|
|
35
|
+
*
|
|
36
|
+
* Named after Apple's `imageShape` parameter (a platform passthrough), and
|
|
37
|
+
* deliberately without a grid prefix: every tile presentation that takes a
|
|
38
|
+
* shape reads the same property.
|
|
39
|
+
*/
|
|
40
|
+
export type ImageShape = 'circular' | 'rounded-rectangle'
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The kind of tile a grid renders.
|
|
44
|
+
*
|
|
45
|
+
* - `'plain'` (default) — artwork tiles with a title.
|
|
46
|
+
* - `'card'` — a larger card with title/subtitle and a
|
|
47
|
+
* {@link TrackStyle.cardTint} color; its image mode is
|
|
48
|
+
* {@link TrackStyle.cardImage}. Cards have no shape.
|
|
49
|
+
* - `'condensed'` — a denser cell carrying title + subtitle; takes
|
|
50
|
+
* {@link TrackStyle.imageShape} and {@link TrackStyle.accessorySymbol}
|
|
51
|
+
* like a plain tile.
|
|
52
|
+
*
|
|
53
|
+
* Element families are mutually exclusive within a container, so this is
|
|
54
|
+
* a container property — hence the enum, and the `grid` prefix.
|
|
55
|
+
*/
|
|
56
|
+
export type GridTile = 'plain' | 'card' | 'condensed'
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* How a card tile uses its image.
|
|
60
|
+
*
|
|
61
|
+
* - `'normal'` (default) — image above the labels;
|
|
62
|
+
* {@link TrackStyle.cardTint} colors a gradient behind the labels.
|
|
63
|
+
* - `'background'` — the image fills the card full-height with the labels
|
|
64
|
+
* overlaid; `cardTint` becomes the color behind them. Selects the larger
|
|
65
|
+
* artwork target size.
|
|
66
|
+
*
|
|
67
|
+
* An enum rather than a boolean: it names the card's image mode and
|
|
68
|
+
* leaves room for future modes.
|
|
69
|
+
*/
|
|
70
|
+
export type CardImage = 'normal' | 'background'
|
|
71
|
+
|
|
28
72
|
/**
|
|
29
73
|
* The style declaration block a {@link Track} may carry: *inherited item
|
|
30
74
|
* properties* (resolved `track ?? section ?? page ?? default`, per
|
|
@@ -82,6 +126,58 @@ export interface TrackStyle {
|
|
|
82
126
|
* @platform carplay
|
|
83
127
|
*/
|
|
84
128
|
artworkRendering?: ArtworkRendering
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Inherited (`track ?? section ?? page`): the shape a grid tile's
|
|
132
|
+
* artwork is cropped to — `'circular'` for people, the default
|
|
133
|
+
* `'rounded-rectangle'` for everything else. Shape follows the item
|
|
134
|
+
* wherever it travels: a circular artist stays circular in whatever
|
|
135
|
+
* grid renders it next. Inert where the presentation has no shape
|
|
136
|
+
* (list rows, pre-26 CarPlay, Android Auto).
|
|
137
|
+
*
|
|
138
|
+
* @default 'rounded-rectangle'
|
|
139
|
+
*
|
|
140
|
+
* @platform carplay
|
|
141
|
+
*/
|
|
142
|
+
imageShape?: ImageShape
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Inherited (`track ?? section ?? page`): an SF Symbol name drawn as
|
|
146
|
+
* the item's accessory wherever the current presentation supports one —
|
|
147
|
+
* the trailing edge of a list row, leading the title on an iOS 26 grid
|
|
148
|
+
* tile (placement is the platform's choice per surface). A resolved
|
|
149
|
+
* symbol replaces the derived accessory (the disclosure chevron a
|
|
150
|
+
* browsable row gets); `'none'` clears an inherited value, restoring
|
|
151
|
+
* the derived behavior — inheritance has no other escape, and no SF
|
|
152
|
+
* Symbol is named `none`, so the sentinel is collision-free. Inert off
|
|
153
|
+
* CarPlay.
|
|
154
|
+
*
|
|
155
|
+
* @platform carplay
|
|
156
|
+
*/
|
|
157
|
+
accessorySymbol?: string
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Inherited (`track ?? section ?? page`): the card treatment's tint
|
|
161
|
+
* color (hex, e.g. `'#1e3a8a'`) — a gradient behind the labels, or the
|
|
162
|
+
* color behind them when {@link TrackStyle.cardImage} is
|
|
163
|
+
* `'background'`. Card-prefixed: inert unless the container renders
|
|
164
|
+
* `gridTile: 'card'`.
|
|
165
|
+
*
|
|
166
|
+
* @platform carplay
|
|
167
|
+
*/
|
|
168
|
+
cardTint?: string
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Inherited (`track ?? section ?? page`): how a card tile uses its
|
|
172
|
+
* image — see {@link CardImage}. Uniform via the section value,
|
|
173
|
+
* mixable per card. Inert unless the container renders
|
|
174
|
+
* `gridTile: 'card'`.
|
|
175
|
+
*
|
|
176
|
+
* @default 'normal'
|
|
177
|
+
*
|
|
178
|
+
* @platform carplay
|
|
179
|
+
*/
|
|
180
|
+
cardImage?: CardImage
|
|
85
181
|
}
|
|
86
182
|
|
|
87
183
|
/**
|
|
@@ -113,6 +209,20 @@ export interface SectionStyle extends TrackStyle {
|
|
|
113
209
|
* @see https://developer.apple.com/documentation/carplay/cplistimagerowitem
|
|
114
210
|
*/
|
|
115
211
|
gridWrap?: boolean
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* The kind of tile this container's grid renders — see
|
|
215
|
+
* {@link GridTile}. A container property (element families are
|
|
216
|
+
* container-homogeneous), resolved by scope override
|
|
217
|
+
* (`section ?? page`), never per item. Inert unless
|
|
218
|
+
* {@link TrackStyle.display} is `'grid'`, and on CarPlay before
|
|
219
|
+
* iOS 26 (where the treatment drops and the layout survives).
|
|
220
|
+
*
|
|
221
|
+
* @default 'plain'
|
|
222
|
+
*
|
|
223
|
+
* @platform carplay
|
|
224
|
+
*/
|
|
225
|
+
gridTile?: GridTile
|
|
116
226
|
}
|
|
117
227
|
|
|
118
228
|
/**
|
|
@@ -200,8 +310,8 @@ export interface Section {
|
|
|
200
310
|
/**
|
|
201
311
|
* Navigation target for the section header / "view all" surface. A
|
|
202
312
|
* tile section's header tap (CarPlay) and appended "view all" link
|
|
203
|
-
* (Android Auto) navigate here. Absent = a
|
|
204
|
-
* not
|
|
313
|
+
* (Android Auto) navigate here. Absent = a self-contained section:
|
|
314
|
+
* the header is a plain label, not a link.
|
|
205
315
|
*/
|
|
206
316
|
path?: string
|
|
207
317
|
/** The section's tracks. */
|
|
@@ -160,6 +160,35 @@ describe('QueueManager', () => {
|
|
|
160
160
|
expect(q.nextIndex()).toBeUndefined()
|
|
161
161
|
})
|
|
162
162
|
|
|
163
|
+
// Filling a queue that had no current track: the order is shuffled with
|
|
164
|
+
// nothing to lead it, so the track that becomes current can land anywhere —
|
|
165
|
+
// last included, which reads as end-of-queue from the very first track.
|
|
166
|
+
it('leads the order with the start track when setTracks fills an empty queue', () => {
|
|
167
|
+
const fresh = new QueueManager()
|
|
168
|
+
fresh.setShuffleEnabled(true) // shuffle already on, queue still empty
|
|
169
|
+
fresh.setTracks(tracks(4))
|
|
170
|
+
fresh.currentIndex = 2 // the start index
|
|
171
|
+
|
|
172
|
+
// identity order [0,1,2,3] re-pinned on 2 → [2,1,0,3]
|
|
173
|
+
expect(fresh.previousIndex()).toBeUndefined()
|
|
174
|
+
expect(fresh.nextIndex()).toBe(1)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('leads the order with the start track when insert fills an empty queue', () => {
|
|
178
|
+
const fresh = new QueueManager()
|
|
179
|
+
fresh.setShuffleEnabled(true)
|
|
180
|
+
const random = vi.spyOn(Math, 'random').mockReturnValue(0.999)
|
|
181
|
+
try {
|
|
182
|
+
fresh.insert(tracks(4)) // slotted in at the order's end → [0,1,2,3]
|
|
183
|
+
} finally {
|
|
184
|
+
random.mockRestore()
|
|
185
|
+
}
|
|
186
|
+
fresh.currentIndex = 3 // would sit LAST in the order without the re-pin
|
|
187
|
+
|
|
188
|
+
expect(fresh.nextIndex()).toBe(1)
|
|
189
|
+
expect(fresh.previousIndex()).toBeUndefined()
|
|
190
|
+
})
|
|
191
|
+
|
|
163
192
|
it('keeps every track at its shuffle position across a move', () => {
|
|
164
193
|
q.currentIndex = 1
|
|
165
194
|
q.move(1, 3) // tracks [s0,s2,s3,s1]; order remapped to [0,3,1,2]
|