@applicaster/zapp-react-native-utils 14.0.0-rc.9 → 15.0.0-rc.1
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/actionsExecutor/ActionExecutorContext.tsx +60 -84
- package/actionsExecutor/ScreenActions.ts +164 -0
- package/actionsExecutor/StorageActions.ts +110 -0
- package/actionsExecutor/feedDecorator.ts +171 -0
- package/actionsExecutor/screenResolver.ts +11 -0
- package/analyticsUtils/AnalyticsEvents/helper.ts +81 -0
- package/analyticsUtils/AnalyticsEvents/sendHeaderClickEvent.ts +1 -1
- package/analyticsUtils/AnalyticsEvents/sendMenuClickEvent.ts +2 -1
- package/analyticsUtils/AnalyticsEvents/sendOnClickEvent.ts +14 -4
- package/analyticsUtils/__tests__/analyticsUtils.test.js +3 -0
- package/analyticsUtils/events.ts +8 -0
- package/analyticsUtils/index.tsx +3 -4
- package/analyticsUtils/manager.ts +1 -1
- package/analyticsUtils/playerAnalyticsTracker.ts +2 -1
- package/appUtils/HooksManager/Hook.ts +4 -4
- package/appUtils/HooksManager/index.ts +11 -1
- package/appUtils/accessibilityManager/const.ts +13 -0
- package/appUtils/accessibilityManager/hooks.ts +35 -1
- package/appUtils/accessibilityManager/index.ts +154 -30
- package/appUtils/accessibilityManager/utils.ts +24 -0
- package/appUtils/contextKeysManager/contextResolver.ts +42 -1
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +7 -0
- package/appUtils/focusManager/__tests__/focusManager.test.js +1 -1
- package/appUtils/focusManager/events.ts +2 -0
- package/appUtils/focusManager/index.ios.ts +10 -0
- package/appUtils/focusManager/index.ts +86 -11
- package/appUtils/focusManager/treeDataStructure/Tree/index.js +1 -1
- package/appUtils/focusManagerAux/utils/index.ts +94 -3
- package/appUtils/platform/platformUtils.ts +31 -1
- package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +0 -15
- package/appUtils/playerManager/useChapterMarker.tsx +0 -1
- package/appUtils/playerManager/usePlayerControllerSetup.tsx +16 -0
- package/arrayUtils/__tests__/isEmptyArray.test.ts +63 -0
- package/arrayUtils/__tests__/isFilledArray.test.ts +1 -1
- package/arrayUtils/index.ts +8 -3
- package/audioPlayerUtils/__tests__/getArtworkImage.test.ts +144 -0
- package/audioPlayerUtils/__tests__/getBackgroundImage.test.ts +72 -0
- package/audioPlayerUtils/__tests__/getImageFromEntry.test.ts +110 -0
- package/audioPlayerUtils/assets/index.ts +2 -0
- package/audioPlayerUtils/index.ts +242 -0
- package/componentsUtils/__tests__/isTabsScreen.test.ts +38 -0
- package/componentsUtils/index.ts +4 -1
- package/conf/player/__tests__/selectors.test.ts +34 -0
- package/conf/player/selectors.ts +10 -0
- package/configurationUtils/__tests__/configurationUtils.test.js +0 -31
- package/configurationUtils/__tests__/getMediaItems.test.ts +65 -0
- package/configurationUtils/__tests__/imageSrcFromMediaItem.test.ts +34 -0
- package/configurationUtils/__tests__/manifestKeyParser.test.ts +546 -0
- package/configurationUtils/index.ts +64 -35
- package/configurationUtils/manifestKeyParser.ts +57 -32
- package/focusManager/FocusManager.ts +104 -20
- package/focusManager/Tree.ts +25 -21
- package/focusManager/__tests__/FocusManager.test.ts +50 -8
- package/focusManager/aux/index.ts +98 -0
- package/focusManager/utils.ts +12 -6
- package/index.d.ts +1 -10
- package/manifestUtils/_internals/getDefaultConfiguration.js +28 -0
- package/manifestUtils/{_internals.js → _internals/index.js} +2 -25
- package/manifestUtils/createConfig.js +4 -1
- package/manifestUtils/defaultManifestConfigurations/player.js +2764 -1539
- package/manifestUtils/index.js +4 -0
- package/manifestUtils/keys.js +12 -0
- package/manifestUtils/progressBar/__tests__/mobileProgressBar.test.js +0 -30
- package/manifestUtils/sharedConfiguration/screenPicker/stylesFields.js +6 -0
- package/navigationUtils/__tests__/mapContentTypesToRivers.test.ts +130 -0
- package/navigationUtils/index.ts +7 -5
- package/package.json +2 -3
- package/playerUtils/PlayerTTS/PlayerTTS.ts +359 -0
- package/playerUtils/PlayerTTS/index.ts +1 -0
- package/playerUtils/__tests__/configurationUtils.test.ts +1 -65
- package/playerUtils/__tests__/getPlayerActionButtons.test.ts +54 -0
- package/playerUtils/_internals/__tests__/utils.test.ts +71 -0
- package/playerUtils/_internals/index.ts +1 -0
- package/playerUtils/_internals/utils.ts +31 -0
- package/playerUtils/configurationUtils.ts +0 -44
- package/playerUtils/getPlayerActionButtons.ts +17 -0
- package/playerUtils/index.ts +53 -0
- package/playerUtils/usePlayerTTS.ts +21 -0
- package/playerUtils/useValidatePlayerConfig.tsx +22 -19
- package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +15 -14
- package/reactHooks/cell-click/__tests__/index.test.js +3 -0
- package/reactHooks/cell-click/index.ts +8 -1
- package/reactHooks/debugging/__tests__/index.test.js +0 -1
- package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +47 -90
- package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +71 -31
- package/reactHooks/feed/index.ts +2 -0
- package/reactHooks/feed/useBatchLoading.ts +17 -10
- package/reactHooks/feed/useFeedLoader.tsx +36 -34
- package/reactHooks/feed/useLoadPipesDataDispatch.ts +63 -0
- package/reactHooks/feed/usePipesCacheReset.ts +3 -3
- package/reactHooks/flatList/useSequentialRenderItem.tsx +3 -3
- package/reactHooks/layout/__tests__/index.test.tsx +3 -1
- package/reactHooks/layout/isTablet/index.ts +12 -5
- package/reactHooks/layout/useDimensions/__tests__/useDimensions.test.ts +34 -36
- package/reactHooks/layout/useDimensions/useDimensions.ts +2 -3
- package/reactHooks/layout/useLayoutVersion.ts +5 -5
- package/reactHooks/navigation/index.ts +7 -5
- package/reactHooks/navigation/useIsScreenActive.ts +9 -5
- package/reactHooks/navigation/useRoute.ts +7 -2
- package/reactHooks/navigation/useScreenStateStore.ts +8 -0
- package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +4 -0
- package/reactHooks/screen/useScreenContext.ts +1 -1
- package/reactHooks/state/__tests__/ZStoreProvider.test.tsx +2 -1
- package/reactHooks/state/index.ts +1 -1
- package/reactHooks/state/useHomeRiver.ts +4 -2
- package/reactHooks/state/useRivers.ts +7 -8
- package/riverComponetsMeasurementProvider/index.tsx +1 -1
- package/screenPickerUtils/index.ts +13 -0
- package/services/js2native.ts +1 -0
- package/storage/ScreenSingleValueProvider.ts +204 -0
- package/storage/ScreenStateMultiSelectProvider.ts +293 -0
- package/storage/StorageMultiSelectProvider.ts +192 -0
- package/storage/StorageSingleSelectProvider.ts +108 -0
- package/testUtils/index.tsx +7 -8
- package/time/BackgroundTimer.ts +6 -4
- package/utils/__tests__/endsWith.test.ts +30 -0
- package/utils/__tests__/find.test.ts +36 -0
- package/utils/__tests__/omit.test.ts +19 -0
- package/utils/__tests__/path.test.ts +33 -0
- package/utils/__tests__/pathOr.test.ts +37 -0
- package/utils/__tests__/startsWith.test.ts +30 -0
- package/utils/__tests__/take.test.ts +40 -0
- package/utils/endsWith.ts +9 -0
- package/utils/find.ts +3 -0
- package/utils/index.ts +33 -1
- package/utils/omit.ts +5 -0
- package/utils/path.ts +5 -0
- package/utils/pathOr.ts +5 -0
- package/utils/startsWith.ts +9 -0
- package/utils/take.ts +5 -0
- package/zappFrameworkUtils/HookCallback/callbackNavigationAction.ts +164 -0
- package/zappFrameworkUtils/HookCallback/hookCallbackManifestExtensions.config.js +60 -0
- package/zappFrameworkUtils/HookCallback/useCallbackActions.ts +22 -0
- package/zappFrameworkUtils/loginPluginUtils.ts +1 -1
- package/playerUtils/configurationGenerator.ts +0 -2572
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { ContextKeysManager } from "./index";
|
|
2
2
|
import * as R from "ramda";
|
|
3
|
+
import * as _ from "lodash";
|
|
4
|
+
import { useScreenStateStore } from "../../reactHooks/navigation/useScreenStateStore";
|
|
3
5
|
|
|
4
|
-
interface IResolver {
|
|
6
|
+
export interface IResolver {
|
|
5
7
|
resolve: (string) => Promise<string | number | object>;
|
|
6
8
|
}
|
|
7
9
|
|
|
10
|
+
// TODO: Rename to ObjectKeyResolver or similar
|
|
8
11
|
export class EntryResolver implements IResolver {
|
|
9
12
|
entry: ZappEntry;
|
|
10
13
|
|
|
@@ -21,6 +24,28 @@ export class EntryResolver implements IResolver {
|
|
|
21
24
|
}
|
|
22
25
|
}
|
|
23
26
|
|
|
27
|
+
// TODO: Move to proper place
|
|
28
|
+
|
|
29
|
+
export class ScreenStateResolver implements IResolver {
|
|
30
|
+
constructor(
|
|
31
|
+
private screenStateStore: ReturnType<typeof useScreenStateStore>
|
|
32
|
+
) {}
|
|
33
|
+
|
|
34
|
+
async resolve(key: string) {
|
|
35
|
+
const screenState = this.screenStateStore.getState().data;
|
|
36
|
+
|
|
37
|
+
if (!key || key.length === 0) {
|
|
38
|
+
return screenState;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (key.includes(".")) {
|
|
42
|
+
return R.view(R.lensPath(key.split(".")), screenState);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return screenState?.[key];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
24
49
|
export class ContextResolver implements IResolver {
|
|
25
50
|
resolve = async (compositeKey: string) =>
|
|
26
51
|
ContextKeysManager.instance.getKey(compositeKey);
|
|
@@ -64,3 +89,19 @@ export const resolveObjectValues = async (
|
|
|
64
89
|
|
|
65
90
|
return Object.fromEntries(resolvedEntries);
|
|
66
91
|
};
|
|
92
|
+
|
|
93
|
+
export const extractAtValues = _.memoize((input: any): string[] => {
|
|
94
|
+
return _.flatMapDeep(input, (value: any) => {
|
|
95
|
+
if (_.isString(value)) {
|
|
96
|
+
const matches = value.match(/@\{([^}]*)\}/g);
|
|
97
|
+
|
|
98
|
+
return matches ? matches.map((match) => match.slice(2, -1)) : [];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (_.isObject(value)) {
|
|
102
|
+
return extractAtValues(_.values(value));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return [];
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -6,6 +6,8 @@ exports[`focusManager should be defined 1`] = `
|
|
|
6
6
|
"disableFocus": [Function],
|
|
7
7
|
"enableFocus": [Function],
|
|
8
8
|
"findPreferredFocusChild": [Function],
|
|
9
|
+
"focusOnSelectedTab": [Function],
|
|
10
|
+
"focusOnSelectedTopMenuItem": [Function],
|
|
9
11
|
"focusableTree": Tree {
|
|
10
12
|
"loadingCounter": 0,
|
|
11
13
|
"root": {
|
|
@@ -24,7 +26,11 @@ exports[`focusManager should be defined 1`] = `
|
|
|
24
26
|
"invokeHandler": [Function],
|
|
25
27
|
"isCurrentFocusOnTheTopScreen": [Function],
|
|
26
28
|
"isFocusDisabled": [Function],
|
|
29
|
+
"isFocusOn": [Function],
|
|
30
|
+
"isFocusOnContent": [Function],
|
|
31
|
+
"isFocusOnMenu": [Function],
|
|
27
32
|
"isGroupItemFocused": [Function],
|
|
33
|
+
"isTabsScreenContentFocused": [Function],
|
|
28
34
|
"longPress": [Function],
|
|
29
35
|
"moveFocus": [Function],
|
|
30
36
|
"on": [Function],
|
|
@@ -63,6 +69,7 @@ exports[`focusManagerIOS should be defined 1`] = `
|
|
|
63
69
|
"getGroupRootById": [Function],
|
|
64
70
|
"getPreferredFocusChild": [Function],
|
|
65
71
|
"invokeHandler": [Function],
|
|
72
|
+
"isFocusOn": [Function],
|
|
66
73
|
"isGroupItemFocused": [Function],
|
|
67
74
|
"moveFocus": [Function],
|
|
68
75
|
"on": [Function],
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NativeModules } from "react-native";
|
|
2
2
|
import * as R from "ramda";
|
|
3
3
|
|
|
4
|
+
import { isCurrentFocusOn } from "../focusManagerAux/utils";
|
|
4
5
|
import { Tree } from "./treeDataStructure/Tree";
|
|
5
6
|
import { findFocusableNode } from "./treeDataStructure/Utils";
|
|
6
7
|
import { subscriber } from "../../functionUtils";
|
|
@@ -391,6 +392,14 @@ export const focusManager = (function () {
|
|
|
391
392
|
return node;
|
|
392
393
|
}
|
|
393
394
|
|
|
395
|
+
function isFocusOn(id): boolean {
|
|
396
|
+
const currentFocusNode = focusableTree.findInTree(
|
|
397
|
+
getCurrentFocus()?.props?.id
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
return id && isCurrentFocusOn(id, currentFocusNode);
|
|
401
|
+
}
|
|
402
|
+
|
|
394
403
|
return {
|
|
395
404
|
on,
|
|
396
405
|
invokeHandler,
|
|
@@ -412,5 +421,6 @@ export const focusManager = (function () {
|
|
|
412
421
|
getGroupRootById,
|
|
413
422
|
isGroupItemFocused,
|
|
414
423
|
getPreferredFocusChild,
|
|
424
|
+
isFocusOn,
|
|
415
425
|
};
|
|
416
426
|
})();
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
isNilOrEmpty,
|
|
4
4
|
isNotNil,
|
|
5
5
|
} from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
6
|
+
import { getFocusableId } from "@applicaster/zapp-react-native-utils/screenPickerUtils";
|
|
6
7
|
|
|
7
8
|
import { Tree } from "./treeDataStructure/Tree";
|
|
8
9
|
import {
|
|
@@ -14,6 +15,13 @@ import { subscriber } from "../../functionUtils";
|
|
|
14
15
|
import { coreLogger } from "../../logger";
|
|
15
16
|
import { ACTION } from "./utils/enums";
|
|
16
17
|
|
|
18
|
+
import {
|
|
19
|
+
isTabsScreenOnContentFocused,
|
|
20
|
+
isCurrentFocusOnContent,
|
|
21
|
+
isCurrentFocusOnMenu,
|
|
22
|
+
isCurrentFocusOn,
|
|
23
|
+
} from "../focusManagerAux/utils";
|
|
24
|
+
|
|
17
25
|
const logger = coreLogger.addSubsystem("focusManager");
|
|
18
26
|
|
|
19
27
|
const isFocusEnabled = (focusableItem): boolean => {
|
|
@@ -100,7 +108,7 @@ export const focusManager = (function () {
|
|
|
100
108
|
* @private
|
|
101
109
|
* @param {Object} direction of the navigation which led to this action
|
|
102
110
|
*/
|
|
103
|
-
function focus(direction) {
|
|
111
|
+
function focus(direction, context?: FocusManager.FocusContext) {
|
|
104
112
|
const currentFocusable = getCurrentFocus();
|
|
105
113
|
|
|
106
114
|
if (
|
|
@@ -108,7 +116,7 @@ export const focusManager = (function () {
|
|
|
108
116
|
!currentFocusable.isGroup &&
|
|
109
117
|
currentFocusable.isMounted()
|
|
110
118
|
) {
|
|
111
|
-
currentFocusable.setFocus(direction);
|
|
119
|
+
currentFocusable.setFocus(direction, context);
|
|
112
120
|
}
|
|
113
121
|
}
|
|
114
122
|
|
|
@@ -205,7 +213,7 @@ export const focusManager = (function () {
|
|
|
205
213
|
* @param {Array<string>} ids - An array of node IDs to update.
|
|
206
214
|
* @param {boolean} setFocus - A flag indicating whether to set focus (true) or blur (false) on the nodes.
|
|
207
215
|
*/
|
|
208
|
-
const updateNodeFocus = (ids, action) => {
|
|
216
|
+
const updateNodeFocus = (ids, action, context: FocusManager.FocusContext) => {
|
|
209
217
|
if (!ids || ids.length === 0) {
|
|
210
218
|
return; // Nothing to do
|
|
211
219
|
}
|
|
@@ -222,11 +230,13 @@ export const focusManager = (function () {
|
|
|
222
230
|
|
|
223
231
|
// Function to apply the action (focus or blur)
|
|
224
232
|
const applyAction = (node) => {
|
|
233
|
+
const direction = undefined;
|
|
234
|
+
|
|
225
235
|
if (node && node.component) {
|
|
226
236
|
if (action === "focus") {
|
|
227
|
-
node.component.setFocus();
|
|
237
|
+
node.component.setFocus(direction, context);
|
|
228
238
|
} else if (action === "blur") {
|
|
229
|
-
node.component.setBlur();
|
|
239
|
+
node.component.setBlur(direction, context);
|
|
230
240
|
}
|
|
231
241
|
}
|
|
232
242
|
};
|
|
@@ -253,7 +263,11 @@ export const focusManager = (function () {
|
|
|
253
263
|
* @param {Object} direction of the navigation, which led to this focus change
|
|
254
264
|
* to another group or not. defaults to false
|
|
255
265
|
*/
|
|
256
|
-
function setFocus(
|
|
266
|
+
function setFocus(
|
|
267
|
+
id: string,
|
|
268
|
+
direction?: FocusManager.Web.Direction,
|
|
269
|
+
context?: FocusManager.FocusContext
|
|
270
|
+
) {
|
|
257
271
|
if (focusDisabled) return false;
|
|
258
272
|
|
|
259
273
|
// due to optimisiation it's recommanded to set currentFocusNode before setFocus
|
|
@@ -266,21 +280,68 @@ export const focusManager = (function () {
|
|
|
266
280
|
);
|
|
267
281
|
|
|
268
282
|
// Set focus on current node parents and blur on previous node parents
|
|
269
|
-
updateNodeFocus(currentNodeParentsIDs, ACTION.FOCUS);
|
|
270
|
-
updateNodeFocus(previousNodeParentsIDs, ACTION.BLUR);
|
|
283
|
+
updateNodeFocus(currentNodeParentsIDs, ACTION.FOCUS, context);
|
|
284
|
+
updateNodeFocus(previousNodeParentsIDs, ACTION.BLUR, context);
|
|
271
285
|
|
|
272
286
|
currentFocusNode = focusableTree.findInTree(id);
|
|
273
287
|
}
|
|
274
288
|
|
|
275
289
|
setLastFocusOnParentNode(currentFocusNode);
|
|
276
290
|
|
|
277
|
-
focus(direction);
|
|
291
|
+
focus(direction, context);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function isFocusOnContent() {
|
|
295
|
+
return isCurrentFocusOnContent(currentFocusNode);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function isFocusOnMenu() {
|
|
299
|
+
return isCurrentFocusOnMenu(currentFocusNode);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function landFocusToWithoutScrolling(id) {
|
|
303
|
+
if (id) {
|
|
304
|
+
// set focus on selected menu item
|
|
305
|
+
const direction = undefined;
|
|
306
|
+
|
|
307
|
+
const context: FocusManager.FocusContext = {
|
|
308
|
+
source: "back",
|
|
309
|
+
preserveScroll: true,
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
logger.log({ message: "landFocusTo", data: { id } });
|
|
313
|
+
|
|
314
|
+
blur(direction);
|
|
315
|
+
setFocus(id, direction, context);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function isTabsScreenContentFocused() {
|
|
320
|
+
return isTabsScreenOnContentFocused(currentFocusNode);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function focusOnSelectedTab(item: ZappEntry): void {
|
|
324
|
+
// Move focus to appropriate top navigation tab with context
|
|
325
|
+
const selectedTabId = getFocusableId(item.id);
|
|
326
|
+
|
|
327
|
+
// Set focus with back button context to tabs-menu
|
|
328
|
+
landFocusToWithoutScrolling(selectedTabId);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function focusOnSelectedTopMenuItem(
|
|
332
|
+
selectedMenuItemId: Option<string>
|
|
333
|
+
): void {
|
|
334
|
+
// Set focus with back button context to top-menu
|
|
335
|
+
landFocusToWithoutScrolling(selectedMenuItemId);
|
|
278
336
|
}
|
|
279
337
|
|
|
280
338
|
/**
|
|
281
339
|
* sets the initial focus when the screen loads, or when focus is lost
|
|
282
340
|
*/
|
|
283
|
-
function setInitialFocus(
|
|
341
|
+
function setInitialFocus(
|
|
342
|
+
lastAddedParentNode?: any,
|
|
343
|
+
context?: FocusManager.FocusContext
|
|
344
|
+
) {
|
|
284
345
|
const preferredFocus = findPriorityItem(
|
|
285
346
|
lastAddedParentNode?.children || focusableTree.root.children
|
|
286
347
|
);
|
|
@@ -326,7 +387,7 @@ export const focusManager = (function () {
|
|
|
326
387
|
},
|
|
327
388
|
});
|
|
328
389
|
|
|
329
|
-
focusableItem && setFocus(focusCandidate.id, null);
|
|
390
|
+
focusableItem && setFocus(focusCandidate.id, null, context);
|
|
330
391
|
|
|
331
392
|
return { success: true };
|
|
332
393
|
}
|
|
@@ -546,6 +607,14 @@ export const focusManager = (function () {
|
|
|
546
607
|
return preferredFocus[0];
|
|
547
608
|
}
|
|
548
609
|
|
|
610
|
+
function isFocusOn(id): boolean {
|
|
611
|
+
return (
|
|
612
|
+
id &&
|
|
613
|
+
isCurrentFocusOnTheTopScreen() &&
|
|
614
|
+
isCurrentFocusOn(id, currentFocusNode)
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
|
|
549
618
|
/**
|
|
550
619
|
* this is the list of the functions available externally
|
|
551
620
|
* when importing the focus manager
|
|
@@ -576,5 +645,11 @@ export const focusManager = (function () {
|
|
|
576
645
|
recoverFocus,
|
|
577
646
|
isCurrentFocusOnTheTopScreen,
|
|
578
647
|
findPreferredFocusChild,
|
|
648
|
+
isFocusOnContent,
|
|
649
|
+
isFocusOnMenu,
|
|
650
|
+
isFocusOn,
|
|
651
|
+
focusOnSelectedTopMenuItem,
|
|
652
|
+
focusOnSelectedTab,
|
|
653
|
+
isTabsScreenContentFocused,
|
|
579
654
|
};
|
|
580
655
|
})();
|
|
@@ -1,14 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
isNil,
|
|
3
|
+
last,
|
|
4
|
+
startsWith,
|
|
5
|
+
find,
|
|
6
|
+
pathOr,
|
|
7
|
+
} from "@applicaster/zapp-react-native-utils/utils";
|
|
8
|
+
|
|
3
9
|
import {
|
|
4
10
|
QUICK_BRICK_CONTENT,
|
|
5
11
|
QUICK_BRICK_NAVBAR,
|
|
6
12
|
} from "@applicaster/quick-brick-core/const";
|
|
7
13
|
|
|
14
|
+
import {
|
|
15
|
+
getFocusableId,
|
|
16
|
+
SCREEN_PICKER_CONTAINER,
|
|
17
|
+
} from "@applicaster/zapp-react-native-utils/screenPickerUtils";
|
|
18
|
+
|
|
8
19
|
// run check each 300 ms
|
|
9
20
|
// run this check too often could lead to performance penalty on low-end devices
|
|
10
21
|
const HOW_OFTEN_TO_CHECK_CONDITION = 300; // ms
|
|
11
22
|
|
|
23
|
+
const isTopMenu = (node) => startsWith(QUICK_BRICK_NAVBAR, node?.id);
|
|
24
|
+
const isContent = (node) => startsWith(QUICK_BRICK_CONTENT, node?.id);
|
|
25
|
+
const isRoot = (node) => node?.id === "root";
|
|
26
|
+
|
|
27
|
+
const isScrenPicker = (node) => startsWith(SCREEN_PICKER_CONTAINER, node?.id);
|
|
28
|
+
|
|
12
29
|
type Props = {
|
|
13
30
|
maxTimeout: number;
|
|
14
31
|
conditionFn: () => boolean;
|
|
@@ -49,7 +66,7 @@ export const waitForActiveScreen = (currentRoute: string, focusableTree) => {
|
|
|
49
66
|
|
|
50
67
|
const route = find((route) => route.id === currentRoute, routes);
|
|
51
68
|
|
|
52
|
-
return
|
|
69
|
+
return !isNil(route);
|
|
53
70
|
};
|
|
54
71
|
|
|
55
72
|
return waitUntil({
|
|
@@ -99,3 +116,77 @@ export const waitForContent = (focusableTree) => {
|
|
|
99
116
|
conditionFn: contentHasAnyChildren,
|
|
100
117
|
});
|
|
101
118
|
};
|
|
119
|
+
|
|
120
|
+
export const findSelectedTabId = (item: ZappEntry): string => {
|
|
121
|
+
const selectedTabId = getFocusableId(item.id);
|
|
122
|
+
|
|
123
|
+
return selectedTabId;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const isTabsScreenOnContentFocused = (node) => {
|
|
127
|
+
if (isRoot(node)) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (isTopMenu(node)) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (isContent(node)) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (isScrenPicker(node)) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return isTabsScreenOnContentFocused(node.parent);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const isCurrentFocusOnMenu = (node) => {
|
|
147
|
+
if (isRoot(node)) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (isTopMenu(node)) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (isContent(node)) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return isCurrentFocusOnMenu(node.parent);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export const isCurrentFocusOnContent = (node) => {
|
|
163
|
+
if (isRoot(node)) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (isTopMenu(node)) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (isContent(node)) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return isCurrentFocusOnContent(node.parent);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export const isCurrentFocusOn = (id, node) => {
|
|
179
|
+
if (!node) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (isRoot(node)) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (node?.id === id) {
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return isCurrentFocusOn(id, node.parent);
|
|
192
|
+
};
|
|
@@ -10,6 +10,7 @@ import { PLATFORM_KEYS, PLATFORMS, ZappPlatform } from "./const";
|
|
|
10
10
|
import { createLogger, utilsLogger } from "../../logger";
|
|
11
11
|
import { getPlatform } from "../../reactUtils";
|
|
12
12
|
import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
|
|
13
|
+
import { calculateReadingTime } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager/utils";
|
|
13
14
|
|
|
14
15
|
const { log_debug } = createLogger({
|
|
15
16
|
category: "General",
|
|
@@ -212,9 +213,16 @@ export class TTSManager {
|
|
|
212
213
|
}
|
|
213
214
|
|
|
214
215
|
readText(text: string) {
|
|
216
|
+
this.ttsState$.next(true);
|
|
217
|
+
|
|
215
218
|
if (isSamsungPlatform() && window.speechSynthesis) {
|
|
216
219
|
const utterance = new SpeechSynthesisUtterance(text);
|
|
220
|
+
|
|
221
|
+
window.speechSynthesis.cancel(); // Cancel previous speech before speaking new text
|
|
217
222
|
window.speechSynthesis.speak(utterance);
|
|
223
|
+
|
|
224
|
+
// Estimate reading time and set inactive when done
|
|
225
|
+
this.scheduleTTSComplete(text);
|
|
218
226
|
}
|
|
219
227
|
|
|
220
228
|
if (isLgPlatform() && window.webOS?.service) {
|
|
@@ -225,23 +233,45 @@ export class TTSManager {
|
|
|
225
233
|
log_debug("There was a failure setting up webOS TTS service", {
|
|
226
234
|
error,
|
|
227
235
|
});
|
|
236
|
+
|
|
237
|
+
this.ttsState$.next(false);
|
|
228
238
|
},
|
|
229
239
|
onSuccess(response: any) {
|
|
230
240
|
log_debug("webOS TTS service is configured successfully", {
|
|
231
241
|
response,
|
|
232
242
|
});
|
|
243
|
+
|
|
244
|
+
// Estimate reading time and set inactive when done
|
|
245
|
+
this.scheduleTTSComplete(text);
|
|
233
246
|
},
|
|
234
247
|
parameters: {
|
|
235
248
|
text,
|
|
249
|
+
clear: true, // Clear any previous speech before speaking new text
|
|
236
250
|
},
|
|
237
251
|
});
|
|
238
252
|
} catch (error) {
|
|
239
253
|
log_debug("webOS TTS service error", { error });
|
|
254
|
+
this.ttsState$.next(false);
|
|
240
255
|
}
|
|
241
256
|
}
|
|
242
257
|
|
|
243
|
-
if (!window.VIZIO?.Chromevox)
|
|
258
|
+
if (!window.VIZIO?.Chromevox) {
|
|
259
|
+
// For platforms without TTS, estimate reading time
|
|
260
|
+
this.scheduleTTSComplete(text);
|
|
261
|
+
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
244
264
|
|
|
245
265
|
window.VIZIO.Chromevox.play(text);
|
|
266
|
+
// Estimate reading time and set inactive when done
|
|
267
|
+
this.scheduleTTSComplete(text);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private scheduleTTSComplete(text: string) {
|
|
271
|
+
const readingTime = calculateReadingTime(text);
|
|
272
|
+
|
|
273
|
+
setTimeout(() => {
|
|
274
|
+
this.ttsState$.next(false);
|
|
275
|
+
}, readingTime);
|
|
246
276
|
}
|
|
247
277
|
}
|
|
@@ -18,13 +18,6 @@ export const { log_verbose, log_debug, log_info, log_error } = createLogger({
|
|
|
18
18
|
parent: utilsLogger,
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
type ActionChapter = {
|
|
22
|
-
type: string;
|
|
23
|
-
options?: {
|
|
24
|
-
title: string;
|
|
25
|
-
};
|
|
26
|
-
};
|
|
27
|
-
|
|
28
21
|
type ChapterMarkerOriginal = {
|
|
29
22
|
id: string;
|
|
30
23
|
title: string;
|
|
@@ -33,14 +26,6 @@ type ChapterMarkerOriginal = {
|
|
|
33
26
|
actions: ActionChapter[];
|
|
34
27
|
};
|
|
35
28
|
|
|
36
|
-
export type ChapterMarkerEvent = {
|
|
37
|
-
id: string;
|
|
38
|
-
title: string;
|
|
39
|
-
start_time: number;
|
|
40
|
-
end_time: number;
|
|
41
|
-
actions: ActionChapter[];
|
|
42
|
-
};
|
|
43
|
-
|
|
44
29
|
export type TitleSummaryEvent = {
|
|
45
30
|
title: string | number;
|
|
46
31
|
summary: string | number;
|
|
@@ -5,6 +5,9 @@ import { playerManager } from "./index";
|
|
|
5
5
|
import { useValidatePlayerConfig } from "../../playerUtils/useValidatePlayerConfig";
|
|
6
6
|
import { PlayerRole } from "./conts";
|
|
7
7
|
import { isAppleTV } from "@applicaster/zapp-react-native-ui-components/Helpers/Platform";
|
|
8
|
+
import { TVSeekController } from "../../reactHooks/player/TVSeekControlller/TVSeekController";
|
|
9
|
+
import { KeyInputHandler } from "../keyInputHandler/KeyInputHandler";
|
|
10
|
+
import { isTV } from "../../reactUtils";
|
|
8
11
|
|
|
9
12
|
// TODO: Rename to ControllerType
|
|
10
13
|
export const usePlayerControllerSetup = ({
|
|
@@ -76,5 +79,18 @@ export const usePlayerControllerSetup = ({
|
|
|
76
79
|
};
|
|
77
80
|
}, [playerId, playerController]);
|
|
78
81
|
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (!isTV()) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (playerController) {
|
|
88
|
+
const seekController = new TVSeekController(playerController);
|
|
89
|
+
playerController.seekController = seekController;
|
|
90
|
+
|
|
91
|
+
return KeyInputHandler.getInstance().addListener(seekController);
|
|
92
|
+
}
|
|
93
|
+
}, [playerController]);
|
|
94
|
+
|
|
79
95
|
return playerController;
|
|
80
96
|
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { isEmptyArray } from "..";
|
|
2
|
+
|
|
3
|
+
describe("isEmptyArray", () => {
|
|
4
|
+
it("non-empty array is not empty", () => {
|
|
5
|
+
const value = [1, 2, 3];
|
|
6
|
+
|
|
7
|
+
expect(isEmptyArray(value)).toBe(false);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("empty array is empty", () => {
|
|
11
|
+
const value = [];
|
|
12
|
+
|
|
13
|
+
expect(isEmptyArray(value)).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("number is not array", () => {
|
|
17
|
+
const value = 123;
|
|
18
|
+
|
|
19
|
+
expect(isEmptyArray(value)).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("string is not array", () => {
|
|
23
|
+
const value = "vfnjdk";
|
|
24
|
+
|
|
25
|
+
expect(isEmptyArray(value)).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("empty string is not array", () => {
|
|
29
|
+
const value = "";
|
|
30
|
+
|
|
31
|
+
expect(isEmptyArray(value)).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("NaN is not array", () => {
|
|
35
|
+
const value = NaN;
|
|
36
|
+
|
|
37
|
+
expect(isEmptyArray(value)).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("object is not array", () => {
|
|
41
|
+
const value = { test: 1 };
|
|
42
|
+
|
|
43
|
+
expect(isEmptyArray(value)).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("empty object is not array", () => {
|
|
47
|
+
const value = {};
|
|
48
|
+
|
|
49
|
+
expect(isEmptyArray(value)).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("undefined is not array", () => {
|
|
53
|
+
const value = undefined;
|
|
54
|
+
|
|
55
|
+
expect(isEmptyArray(value)).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("null is not array", () => {
|
|
59
|
+
const value = null;
|
|
60
|
+
|
|
61
|
+
expect(isEmptyArray(value)).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
});
|
package/arrayUtils/index.ts
CHANGED
|
@@ -93,18 +93,23 @@ export const isIndexInRange = (index: number, length: number): boolean => {
|
|
|
93
93
|
export const makeListOfIndexes = (size: number): number[] =>
|
|
94
94
|
Array.from({ length: size }, (_, index) => index);
|
|
95
95
|
|
|
96
|
-
export const makeListOf = (value:
|
|
96
|
+
export const makeListOf = <T>(value: T, size: number): T[] => {
|
|
97
97
|
return Array(size).fill(value);
|
|
98
98
|
};
|
|
99
99
|
|
|
100
100
|
/** Checks if a value is a non-empty array */
|
|
101
101
|
export function isFilledArray(value: unknown): boolean {
|
|
102
|
-
return
|
|
102
|
+
return Array.isArray(value) && value.length > 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Checks if a value is a empty array */
|
|
106
|
+
export function isEmptyArray(value: unknown): boolean {
|
|
107
|
+
return Array.isArray(value) && value.length === 0;
|
|
103
108
|
}
|
|
104
109
|
|
|
105
110
|
// get random item from the list
|
|
106
111
|
export const sample = (xs: unknown[]): unknown => {
|
|
107
|
-
invariant(
|
|
112
|
+
invariant(Array.isArray(xs), `input value is not an array: ${xs}`);
|
|
108
113
|
invariant(isFilledArray(xs), `input array is empty: ${xs}`);
|
|
109
114
|
|
|
110
115
|
const index = Math.floor(Math.random() * xs.length);
|