@applicaster/zapp-react-native-utils 15.1.6 → 15.1.7-alpha.1530511842
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/actionUtils/index.ts +7 -0
- package/actionsExecutor/actions/__tests__/showToast.test.ts +6 -3
- package/actionsExecutor/actions/showToast.ts +56 -2
- package/appUtils/playerManager/OverlayObserver/__tests__/utils.test.ts +41 -0
- package/appUtils/playerManager/OverlayObserver/utils.ts +3 -0
- package/manifestUtils/__tests__/player.audioControls.test.js +158 -0
- package/manifestUtils/defaultManifestConfigurations/player.js +156 -0
- package/package.json +2 -2
- package/reactHooks/index.ts +2 -0
- package/reactHooks/utils/__tests__/index.test.js +22 -2
- package/reactHooks/utils/index.ts +11 -1
|
@@ -11,7 +11,7 @@ describe("showToastAction", () => {
|
|
|
11
11
|
jest.clearAllMocks();
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
it("calls postEvent with
|
|
14
|
+
it("calls postEvent with confirmation source, default timeout, and returns Success", async () => {
|
|
15
15
|
const action = {
|
|
16
16
|
type: "showToast",
|
|
17
17
|
options: {
|
|
@@ -31,7 +31,8 @@ describe("showToastAction", () => {
|
|
|
31
31
|
message: "Added to queue",
|
|
32
32
|
extraMessage: "Extra info",
|
|
33
33
|
style: undefined,
|
|
34
|
-
timeout:
|
|
34
|
+
timeout: 1000,
|
|
35
|
+
source: "confirmation",
|
|
35
36
|
},
|
|
36
37
|
]);
|
|
37
38
|
|
|
@@ -57,6 +58,7 @@ describe("showToastAction", () => {
|
|
|
57
58
|
extraMessage: undefined,
|
|
58
59
|
style: { backgroundColor: "#00FF00" },
|
|
59
60
|
timeout: 3000,
|
|
61
|
+
source: "confirmation",
|
|
60
62
|
},
|
|
61
63
|
]);
|
|
62
64
|
});
|
|
@@ -76,7 +78,8 @@ describe("showToastAction", () => {
|
|
|
76
78
|
message: undefined,
|
|
77
79
|
extraMessage: undefined,
|
|
78
80
|
style: undefined,
|
|
79
|
-
timeout:
|
|
81
|
+
timeout: 1000,
|
|
82
|
+
source: "confirmation",
|
|
80
83
|
},
|
|
81
84
|
]);
|
|
82
85
|
|
|
@@ -3,8 +3,61 @@ import { ActionResult } from "../ActionExecutor";
|
|
|
3
3
|
import { ActionHandler } from "../types";
|
|
4
4
|
import { postEvent } from "../../reactHooks/useSubscriberFor";
|
|
5
5
|
|
|
6
|
+
export const CONFIRMATION_TOAST_SOURCE = "confirmation";
|
|
7
|
+
|
|
8
|
+
export const CONFIRMATION_TOAST_DEFAULT_TIMEOUT = 1000;
|
|
9
|
+
|
|
6
10
|
/**
|
|
7
|
-
*
|
|
11
|
+
* Shows a mobile confirmation toast with Theme defaults from
|
|
12
|
+
* `Confirmation Toast / Message` in the base theme plugin.
|
|
13
|
+
*
|
|
14
|
+
* This action is confirmation-only by design: it always stamps
|
|
15
|
+
* `source: "confirmation"` (so Theme confirmation styles apply) and defaults
|
|
16
|
+
* `timeout` to 1000 ms. Non-confirmation toasts (e.g. Offline Experience)
|
|
17
|
+
* must post on the shared `"showToast"` event bus directly and omit `source`.
|
|
18
|
+
* Callers may override individual style fields; Theme supplies the rest.
|
|
19
|
+
*
|
|
20
|
+
* @example From React via ActionExecutorContext
|
|
21
|
+
* ```tsx
|
|
22
|
+
* import React, { useContext } from "react";
|
|
23
|
+
* import { ActionExecutorContext } from "@applicaster/zapp-react-native-utils/actionsExecutor/ActionExecutorContext";
|
|
24
|
+
*
|
|
25
|
+
* function QueueButton() {
|
|
26
|
+
* const { handleAction } = useContext(ActionExecutorContext);
|
|
27
|
+
*
|
|
28
|
+
* const onPress = () => {
|
|
29
|
+
* void handleAction({
|
|
30
|
+
* type: "showToast",
|
|
31
|
+
* options: { message: "Added to queue" },
|
|
32
|
+
* });
|
|
33
|
+
* };
|
|
34
|
+
*
|
|
35
|
+
* return <Button onPress={onPress} title="Add to queue" />;
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @example From code via the actionExecutor singleton
|
|
40
|
+
* ```ts
|
|
41
|
+
* import { actionExecutor } from "@applicaster/zapp-react-native-utils/actionsExecutor/ActionExecutor";
|
|
42
|
+
*
|
|
43
|
+
* await actionExecutor.handleAction({
|
|
44
|
+
* type: "showToast",
|
|
45
|
+
* options: {
|
|
46
|
+
* message: "Playlist created",
|
|
47
|
+
* style: { backgroundColor: "#00AA55" }, // optional partial override
|
|
48
|
+
* },
|
|
49
|
+
* });
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @example From entry / pipes actions JSON
|
|
53
|
+
* ```json
|
|
54
|
+
* {
|
|
55
|
+
* "type": "showToast",
|
|
56
|
+
* "options": {
|
|
57
|
+
* "message": "Item removed from playlist"
|
|
58
|
+
* }
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
8
61
|
*/
|
|
9
62
|
export interface ShowToastActionOptions {
|
|
10
63
|
id?: string;
|
|
@@ -25,7 +78,8 @@ export const showToastAction: ActionHandler<ShowToastActionOptions> = async (
|
|
|
25
78
|
message: options?.message,
|
|
26
79
|
extraMessage: options?.extraMessage,
|
|
27
80
|
style: options?.style,
|
|
28
|
-
timeout: options?.timeout,
|
|
81
|
+
timeout: options?.timeout ?? CONFIRMATION_TOAST_DEFAULT_TIMEOUT,
|
|
82
|
+
source: CONFIRMATION_TOAST_SOURCE,
|
|
29
83
|
},
|
|
30
84
|
]);
|
|
31
85
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { retrieveFeedUrl, retrievePlayPreviousFeedUrl } from "../utils";
|
|
2
|
+
|
|
3
|
+
const entryWith = (extensions: Record<string, unknown>): ZappEntry =>
|
|
4
|
+
({ id: "1", type: { value: "video" }, extensions }) as ZappEntry;
|
|
5
|
+
|
|
6
|
+
describe("playlist feed url accessors", () => {
|
|
7
|
+
it("returns the next feed url when present", () => {
|
|
8
|
+
const entry = entryWith({ play_next_feed_url: "https://feeds/next.json" });
|
|
9
|
+
|
|
10
|
+
expect(retrieveFeedUrl(entry)).toBe("https://feeds/next.json");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("returns the previous feed url when present", () => {
|
|
14
|
+
const entry = entryWith({
|
|
15
|
+
play_previous_feed_url: "https://feeds/prev.json",
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
expect(retrievePlayPreviousFeedUrl(entry)).toBe("https://feeds/prev.json");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("returns undefined when only the next url is present", () => {
|
|
22
|
+
const entry = entryWith({ play_next_feed_url: "https://feeds/next.json" });
|
|
23
|
+
|
|
24
|
+
expect(retrievePlayPreviousFeedUrl(entry)).toBeUndefined();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("returns undefined for an entry with no extensions", () => {
|
|
28
|
+
expect(
|
|
29
|
+
retrievePlayPreviousFeedUrl({
|
|
30
|
+
id: "1",
|
|
31
|
+
type: { value: "video" },
|
|
32
|
+
} as ZappEntry)
|
|
33
|
+
).toBeUndefined();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("returns undefined for a missing entry", () => {
|
|
37
|
+
expect(
|
|
38
|
+
retrievePlayPreviousFeedUrl(undefined as unknown as ZappEntry)
|
|
39
|
+
).toBeUndefined();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -43,6 +43,9 @@ export const parseTimeToSeconds = (timeStr: string): number => {
|
|
|
43
43
|
export const retrieveFeedUrl = (entry: ZappEntry) =>
|
|
44
44
|
entry?.extensions?.play_next_feed_url;
|
|
45
45
|
|
|
46
|
+
export const retrievePlayPreviousFeedUrl = (entry: ZappEntry) =>
|
|
47
|
+
entry?.extensions?.play_previous_feed_url;
|
|
48
|
+
|
|
46
49
|
export const retrieveOverlayDuration = (plugin) =>
|
|
47
50
|
toNumberWithDefault(plugin?.configuration?.overlay_duration, 15);
|
|
48
51
|
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
const { player } = require("../defaultManifestConfigurations/player");
|
|
2
|
+
|
|
3
|
+
const manifestFor = (platform) => player({ platform, version: "0.0.0" });
|
|
4
|
+
|
|
5
|
+
const findGroup = (manifest, label) =>
|
|
6
|
+
manifest.styles.fields.find((group) => group.group && group.label === label);
|
|
7
|
+
|
|
8
|
+
const findFieldInGroup = (manifest, label, key) =>
|
|
9
|
+
findGroup(manifest, label)?.fields?.find((field) => field.key === key);
|
|
10
|
+
|
|
11
|
+
const findLocalization = (manifest, key) =>
|
|
12
|
+
manifest.localizations.fields.find((field) => field.key === key);
|
|
13
|
+
|
|
14
|
+
describe("player manifest — audio playlist controls", () => {
|
|
15
|
+
const manifest = manifestFor("ios_for_quickbrick");
|
|
16
|
+
|
|
17
|
+
describe("Player Controls Layout", () => {
|
|
18
|
+
const field = () =>
|
|
19
|
+
findFieldInGroup(manifest, "Audio Player", "player_controls_layout");
|
|
20
|
+
|
|
21
|
+
it("is declared in the Audio Player group", () => {
|
|
22
|
+
expect(field()).toBeDefined();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("is a select defaulting to the existing controls", () => {
|
|
26
|
+
expect(field().type).toBe("select");
|
|
27
|
+
expect(field().initial_value).toBe("playback_speed_and_sleep_timer");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("only shows when the audio player layout is in use", () => {
|
|
31
|
+
expect(field()).toMatchObject({
|
|
32
|
+
rules: "conditional",
|
|
33
|
+
conditional_fields: [
|
|
34
|
+
{ key: "styles/full_screen_audio_player", condition_value: true },
|
|
35
|
+
],
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("offers exactly the two documented options", () => {
|
|
40
|
+
expect(field().options).toEqual([
|
|
41
|
+
{
|
|
42
|
+
text: "Playback Speed & Sleep Timer (Default)",
|
|
43
|
+
value: "playback_speed_and_sleep_timer",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
text: "Playlist Controls (Previous & Next)",
|
|
47
|
+
value: "playlist_controls",
|
|
48
|
+
},
|
|
49
|
+
]);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("control icons", () => {
|
|
54
|
+
it.each(["previous_track", "next_track"])(
|
|
55
|
+
"declares %s as an uploader",
|
|
56
|
+
(key) => {
|
|
57
|
+
expect(findFieldInGroup(manifest, "Assets", key)).toMatchObject({
|
|
58
|
+
key,
|
|
59
|
+
type: "uploader",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
it.each(["previous_track", "next_track"])(
|
|
65
|
+
"only shows %s when the playlist controls layout is selected",
|
|
66
|
+
(key) => {
|
|
67
|
+
expect(findFieldInGroup(manifest, "Assets", key)).toMatchObject({
|
|
68
|
+
rules: "conditional",
|
|
69
|
+
conditional_fields: [
|
|
70
|
+
{
|
|
71
|
+
key: "styles/player_controls_layout",
|
|
72
|
+
condition_value: "playlist_controls",
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
it.each([
|
|
80
|
+
"speed_0_8",
|
|
81
|
+
"speed_1",
|
|
82
|
+
"speed_1_2",
|
|
83
|
+
"speed_1_5",
|
|
84
|
+
"speed_2",
|
|
85
|
+
"sleep_timer",
|
|
86
|
+
"sleep_timer_active",
|
|
87
|
+
])(
|
|
88
|
+
"only shows %s when the playback speed and sleep timer layout is selected",
|
|
89
|
+
(key) => {
|
|
90
|
+
expect(findFieldInGroup(manifest, "Assets", key)).toMatchObject({
|
|
91
|
+
rules: "conditional",
|
|
92
|
+
conditional_fields: [
|
|
93
|
+
{
|
|
94
|
+
key: "styles/player_controls_layout",
|
|
95
|
+
condition_value: "playback_speed_and_sleep_timer",
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("accessibility strings", () => {
|
|
104
|
+
it.each([
|
|
105
|
+
"accessibility_previous_track_label",
|
|
106
|
+
"accessibility_previous_track_hint",
|
|
107
|
+
"accessibility_next_track_label",
|
|
108
|
+
"accessibility_next_track_hint",
|
|
109
|
+
])("declares %s with a default", (key) => {
|
|
110
|
+
expect(findLocalization(manifest, key)).toMatchObject({
|
|
111
|
+
type: "text_input",
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(findLocalization(manifest, key).initial_value).toBeTruthy();
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe("tv platforms", () => {
|
|
119
|
+
// The audio player layout is mobile only, so nothing this feature adds —
|
|
120
|
+
// including its screen reader strings — belongs in a tv manifest.
|
|
121
|
+
it.each(["tvos_for_quickbrick", "android_tv_for_quickbrick", "lg_tv"])(
|
|
122
|
+
"adds no audio controls layout field on %s",
|
|
123
|
+
(platform) => {
|
|
124
|
+
expect(
|
|
125
|
+
findFieldInGroup(
|
|
126
|
+
manifestFor(platform),
|
|
127
|
+
"Audio Player",
|
|
128
|
+
"player_controls_layout"
|
|
129
|
+
)
|
|
130
|
+
).toBeUndefined();
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
it.each([
|
|
135
|
+
"accessibility_previous_track_label",
|
|
136
|
+
"accessibility_previous_track_hint",
|
|
137
|
+
"accessibility_next_track_label",
|
|
138
|
+
"accessibility_next_track_hint",
|
|
139
|
+
])("does not declare %s on tv", (key) => {
|
|
140
|
+
expect(
|
|
141
|
+
findLocalization(manifestFor("tvos_for_quickbrick"), key)
|
|
142
|
+
).toBeUndefined();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("still declares the tv-only localizations", () => {
|
|
146
|
+
expect(
|
|
147
|
+
findLocalization(
|
|
148
|
+
manifestFor("tvos_for_quickbrick"),
|
|
149
|
+
"back_to_live_label"
|
|
150
|
+
)
|
|
151
|
+
).toBeDefined();
|
|
152
|
+
|
|
153
|
+
expect(
|
|
154
|
+
findLocalization(manifestFor("tvos_for_quickbrick"), "start_over_label")
|
|
155
|
+
).toBeDefined();
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -369,6 +369,58 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
369
369
|
],
|
|
370
370
|
};
|
|
371
371
|
|
|
372
|
+
if (isTV(platform)) {
|
|
373
|
+
localizations.fields.push({
|
|
374
|
+
key: "back_to_live_label",
|
|
375
|
+
label: "Back to live label",
|
|
376
|
+
initial_value: "Back To Live",
|
|
377
|
+
type: "text_input",
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
localizations.fields.push({
|
|
381
|
+
key: "start_over_label",
|
|
382
|
+
label: "Start over label",
|
|
383
|
+
initial_value: "Start Over",
|
|
384
|
+
type: "text_input",
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (isMobile(platform)) {
|
|
389
|
+
// Screen reader strings for the audio player's playlist controls. Mobile
|
|
390
|
+
// only, because the audio player layout itself is mobile only.
|
|
391
|
+
localizations.fields.push({
|
|
392
|
+
key: "accessibility_previous_track_label",
|
|
393
|
+
label: "Accessibility previous track label",
|
|
394
|
+
initial_value: "Previous track button",
|
|
395
|
+
label_tooltip: "Label for previous track button accessibility",
|
|
396
|
+
type: "text_input",
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
localizations.fields.push({
|
|
400
|
+
key: "accessibility_previous_track_hint",
|
|
401
|
+
label: "Accessibility previous track hint",
|
|
402
|
+
initial_value: "Press to play the previous track",
|
|
403
|
+
label_tooltip: "Hint for previous track button accessibility",
|
|
404
|
+
type: "text_input",
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
localizations.fields.push({
|
|
408
|
+
key: "accessibility_next_track_label",
|
|
409
|
+
label: "Accessibility next track label",
|
|
410
|
+
initial_value: "Next track button",
|
|
411
|
+
label_tooltip: "Label for next track button accessibility",
|
|
412
|
+
type: "text_input",
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
localizations.fields.push({
|
|
416
|
+
key: "accessibility_next_track_hint",
|
|
417
|
+
label: "Accessibility next track hint",
|
|
418
|
+
initial_value: "Press to play the next track",
|
|
419
|
+
label_tooltip: "Hint for next track button accessibility",
|
|
420
|
+
type: "text_input",
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
372
424
|
if (isTV(platform)) {
|
|
373
425
|
styles.fields.push(
|
|
374
426
|
fieldsGroup("Always Show Scrub Bar & Timestamp", "", [
|
|
@@ -1163,6 +1215,13 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
1163
1215
|
type: "uploader",
|
|
1164
1216
|
label_tooltip: "Playback Speed 0.8x",
|
|
1165
1217
|
initial_value: null,
|
|
1218
|
+
rules: "conditional",
|
|
1219
|
+
conditional_fields: [
|
|
1220
|
+
{
|
|
1221
|
+
key: "styles/player_controls_layout",
|
|
1222
|
+
condition_value: "playback_speed_and_sleep_timer",
|
|
1223
|
+
},
|
|
1224
|
+
],
|
|
1166
1225
|
},
|
|
1167
1226
|
{
|
|
1168
1227
|
key: "speed_1",
|
|
@@ -1170,6 +1229,13 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
1170
1229
|
type: "uploader",
|
|
1171
1230
|
label_tooltip: "Playback Speed 1x",
|
|
1172
1231
|
initial_value: null,
|
|
1232
|
+
rules: "conditional",
|
|
1233
|
+
conditional_fields: [
|
|
1234
|
+
{
|
|
1235
|
+
key: "styles/player_controls_layout",
|
|
1236
|
+
condition_value: "playback_speed_and_sleep_timer",
|
|
1237
|
+
},
|
|
1238
|
+
],
|
|
1173
1239
|
},
|
|
1174
1240
|
{
|
|
1175
1241
|
key: "speed_1_2",
|
|
@@ -1177,6 +1243,13 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
1177
1243
|
type: "uploader",
|
|
1178
1244
|
label_tooltip: "Playback Speed 1.2x",
|
|
1179
1245
|
initial_value: null,
|
|
1246
|
+
rules: "conditional",
|
|
1247
|
+
conditional_fields: [
|
|
1248
|
+
{
|
|
1249
|
+
key: "styles/player_controls_layout",
|
|
1250
|
+
condition_value: "playback_speed_and_sleep_timer",
|
|
1251
|
+
},
|
|
1252
|
+
],
|
|
1180
1253
|
},
|
|
1181
1254
|
{
|
|
1182
1255
|
key: "speed_1_5",
|
|
@@ -1184,6 +1257,13 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
1184
1257
|
type: "uploader",
|
|
1185
1258
|
label_tooltip: "Playback Speed 1.5x",
|
|
1186
1259
|
initial_value: null,
|
|
1260
|
+
rules: "conditional",
|
|
1261
|
+
conditional_fields: [
|
|
1262
|
+
{
|
|
1263
|
+
key: "styles/player_controls_layout",
|
|
1264
|
+
condition_value: "playback_speed_and_sleep_timer",
|
|
1265
|
+
},
|
|
1266
|
+
],
|
|
1187
1267
|
},
|
|
1188
1268
|
{
|
|
1189
1269
|
key: "speed_2",
|
|
@@ -1191,6 +1271,13 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
1191
1271
|
type: "uploader",
|
|
1192
1272
|
label_tooltip: "Playback Speed 2x",
|
|
1193
1273
|
initial_value: null,
|
|
1274
|
+
rules: "conditional",
|
|
1275
|
+
conditional_fields: [
|
|
1276
|
+
{
|
|
1277
|
+
key: "styles/player_controls_layout",
|
|
1278
|
+
condition_value: "playback_speed_and_sleep_timer",
|
|
1279
|
+
},
|
|
1280
|
+
],
|
|
1194
1281
|
},
|
|
1195
1282
|
{
|
|
1196
1283
|
key: "sleep_timer",
|
|
@@ -1198,6 +1285,13 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
1198
1285
|
type: "uploader",
|
|
1199
1286
|
label_tooltip: "Sleep Timer Default",
|
|
1200
1287
|
initial_value: null,
|
|
1288
|
+
rules: "conditional",
|
|
1289
|
+
conditional_fields: [
|
|
1290
|
+
{
|
|
1291
|
+
key: "styles/player_controls_layout",
|
|
1292
|
+
condition_value: "playback_speed_and_sleep_timer",
|
|
1293
|
+
},
|
|
1294
|
+
],
|
|
1201
1295
|
},
|
|
1202
1296
|
{
|
|
1203
1297
|
key: "sleep_timer_active",
|
|
@@ -1205,6 +1299,43 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
1205
1299
|
type: "uploader",
|
|
1206
1300
|
label_tooltip: "Sleep Timer Active",
|
|
1207
1301
|
initial_value: null,
|
|
1302
|
+
rules: "conditional",
|
|
1303
|
+
conditional_fields: [
|
|
1304
|
+
{
|
|
1305
|
+
key: "styles/player_controls_layout",
|
|
1306
|
+
condition_value: "playback_speed_and_sleep_timer",
|
|
1307
|
+
},
|
|
1308
|
+
],
|
|
1309
|
+
},
|
|
1310
|
+
{
|
|
1311
|
+
key: "previous_track",
|
|
1312
|
+
label: "Previous Track Control Icon",
|
|
1313
|
+
type: "uploader",
|
|
1314
|
+
label_tooltip:
|
|
1315
|
+
"Override the default Previous Track icon. Recommended size 120x120px (@3x).",
|
|
1316
|
+
initial_value: null,
|
|
1317
|
+
rules: "conditional",
|
|
1318
|
+
conditional_fields: [
|
|
1319
|
+
{
|
|
1320
|
+
key: "styles/player_controls_layout",
|
|
1321
|
+
condition_value: "playlist_controls",
|
|
1322
|
+
},
|
|
1323
|
+
],
|
|
1324
|
+
},
|
|
1325
|
+
{
|
|
1326
|
+
key: "next_track",
|
|
1327
|
+
label: "Next Track Control Icon",
|
|
1328
|
+
type: "uploader",
|
|
1329
|
+
label_tooltip:
|
|
1330
|
+
"Override the default Next Track icon. Recommended size 120x120px (@3x).",
|
|
1331
|
+
initial_value: null,
|
|
1332
|
+
rules: "conditional",
|
|
1333
|
+
conditional_fields: [
|
|
1334
|
+
{
|
|
1335
|
+
key: "styles/player_controls_layout",
|
|
1336
|
+
condition_value: "playlist_controls",
|
|
1337
|
+
},
|
|
1338
|
+
],
|
|
1208
1339
|
},
|
|
1209
1340
|
]
|
|
1210
1341
|
),
|
|
@@ -3393,6 +3524,31 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
3393
3524
|
},
|
|
3394
3525
|
],
|
|
3395
3526
|
},
|
|
3527
|
+
{
|
|
3528
|
+
key: "player_controls_layout",
|
|
3529
|
+
label: "Player Controls Layout",
|
|
3530
|
+
type: "select",
|
|
3531
|
+
initial_value: "playback_speed_and_sleep_timer",
|
|
3532
|
+
label_tooltip:
|
|
3533
|
+
"Choose which secondary controls appear in the audio player transport row.",
|
|
3534
|
+
rules: "conditional",
|
|
3535
|
+
conditional_fields: [
|
|
3536
|
+
{
|
|
3537
|
+
key: "styles/full_screen_audio_player",
|
|
3538
|
+
condition_value: true,
|
|
3539
|
+
},
|
|
3540
|
+
],
|
|
3541
|
+
options: [
|
|
3542
|
+
{
|
|
3543
|
+
text: "Playback Speed & Sleep Timer (Default)",
|
|
3544
|
+
value: "playback_speed_and_sleep_timer",
|
|
3545
|
+
},
|
|
3546
|
+
{
|
|
3547
|
+
text: "Playlist Controls (Previous & Next)",
|
|
3548
|
+
value: "playlist_controls",
|
|
3549
|
+
},
|
|
3550
|
+
],
|
|
3551
|
+
},
|
|
3396
3552
|
{
|
|
3397
3553
|
key: "audio_player_artwork_shadow_enabled",
|
|
3398
3554
|
label: "Image Shadow Enabled",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-utils",
|
|
3
|
-
"version": "15.1.
|
|
3
|
+
"version": "15.1.7-alpha.1530511842",
|
|
4
4
|
"description": "Applicaster Zapp React Native utilities package",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@applicaster/applicaster-types": "15.1.
|
|
30
|
+
"@applicaster/applicaster-types": "15.1.7-alpha.1530511842",
|
|
31
31
|
"buffer": "^5.2.1",
|
|
32
32
|
"camelize": "^1.0.0",
|
|
33
33
|
"dayjs": "^1.11.10",
|
package/reactHooks/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { usePrevious, useIsInitialRender } from "../";
|
|
2
|
-
import { renderHook, cleanup } from "@testing-library/react-
|
|
1
|
+
import { usePrevious, useIsInitialRender, useForceUpdate } from "../";
|
|
2
|
+
import { renderHook, cleanup, act } from "@testing-library/react-native";
|
|
3
3
|
|
|
4
4
|
describe("usePrevious", () => {
|
|
5
5
|
it("renders", () => {
|
|
@@ -49,3 +49,23 @@ describe("useIsInitialRender", () => {
|
|
|
49
49
|
expect(result.current).toBe(false);
|
|
50
50
|
});
|
|
51
51
|
});
|
|
52
|
+
|
|
53
|
+
describe("useForceUpdate", () => {
|
|
54
|
+
it("forces component to re-render when called", () => {
|
|
55
|
+
let renderCount = 0;
|
|
56
|
+
|
|
57
|
+
const { result } = renderHook(() => {
|
|
58
|
+
renderCount++;
|
|
59
|
+
|
|
60
|
+
return useForceUpdate();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(renderCount).toBe(1);
|
|
64
|
+
|
|
65
|
+
act(() => {
|
|
66
|
+
result.current();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect(renderCount).toBe(2);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useReducer, useRef } from "react";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* This hook returns a previous value that was passed to it.
|
|
@@ -31,6 +31,16 @@ export const useIsInitialRender = (): boolean => {
|
|
|
31
31
|
return isInitialRenderRef.current;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Returns a function that forces a re-render of the component when invoked.
|
|
36
|
+
* @return {() => void}
|
|
37
|
+
*/
|
|
38
|
+
export const useForceUpdate = (): (() => void) => {
|
|
39
|
+
const [, forceUpdate] = useReducer((n: number) => n + 1, 0);
|
|
40
|
+
|
|
41
|
+
return forceUpdate;
|
|
42
|
+
};
|
|
43
|
+
|
|
34
44
|
export const checkIsLocalFeed = (feedUrl) => feedUrl?.startsWith("pipesv2://");
|
|
35
45
|
|
|
36
46
|
export const shouldDispatchData = (
|