@applicaster/zapp-react-native-utils 14.0.0-rc.4 → 14.0.0-rc.41
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 +1 -1
- 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 +14 -0
- package/analyticsUtils/events.ts +8 -0
- package/analyticsUtils/index.tsx +3 -4
- package/analyticsUtils/manager.ts +1 -1
- package/appUtils/HooksManager/Hook.ts +4 -4
- package/appUtils/HooksManager/index.ts +11 -1
- package/appUtils/accessibilityManager/index.ts +7 -4
- package/appUtils/focusManager/treeDataStructure/Tree/index.js +1 -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 +547 -0
- package/configurationUtils/index.ts +63 -34
- package/configurationUtils/manifestKeyParser.ts +57 -32
- package/focusManager/FocusManager.ts +26 -16
- package/focusManager/Tree.ts +25 -21
- package/focusManager/__tests__/FocusManager.test.ts +50 -8
- package/index.d.ts +0 -9
- 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 +1239 -200
- package/manifestUtils/progressBar/__tests__/mobileProgressBar.test.js +0 -30
- package/manifestUtils/sharedConfiguration/screenPicker/stylesFields.js +1 -2
- package/navigationUtils/__tests__/mapContentTypesToRivers.test.ts +130 -0
- package/navigationUtils/index.ts +6 -4
- package/package.json +2 -2
- 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/useValidatePlayerConfig.tsx +22 -19
- package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +15 -14
- package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +39 -88
- package/reactHooks/feed/useBatchLoading.ts +11 -9
- package/reactHooks/feed/useFeedLoader.tsx +12 -8
- 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/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/useRivers.ts +7 -8
- package/riverComponetsMeasurementProvider/index.tsx +1 -1
- package/services/js2native.ts +1 -0
- package/testUtils/index.tsx +7 -8
- package/time/BackgroundTimer.ts +6 -4
- package/utils/index.ts +17 -1
- package/playerUtils/configurationGenerator.ts +0 -2572
|
@@ -4,9 +4,12 @@ import {
|
|
|
4
4
|
ANALYTICS_COMPONENT_EVENTS,
|
|
5
5
|
ANALYTICS_CORE_EVENTS,
|
|
6
6
|
ANALYTICS_ENTRY_EVENTS,
|
|
7
|
+
ANALYTICS_PREFERENCES_EVENTS,
|
|
7
8
|
DOWNLOADS_EVENTS,
|
|
8
9
|
} from "../events";
|
|
9
10
|
import { isEmptyOrNil } from "../../cellUtils";
|
|
11
|
+
import { get } from "lodash";
|
|
12
|
+
import { StorageMultiSelectProvider } from "@applicaster/zapp-react-native-bridge/ZappStorage/StorageMultiSelectProvider";
|
|
10
13
|
|
|
11
14
|
export enum OfflineItemState {
|
|
12
15
|
notExist = "NOT_EXISTS",
|
|
@@ -102,6 +105,84 @@ export function eventForComponent(
|
|
|
102
105
|
return analyticsProps;
|
|
103
106
|
}
|
|
104
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Checks if an item is currently selected in localStorage based on its actions
|
|
110
|
+
* @param item - The item to check
|
|
111
|
+
* @returns boolean indicating if the item is currently selected
|
|
112
|
+
*/
|
|
113
|
+
function isItemPreviouslySelected(item: any): boolean {
|
|
114
|
+
const actions = item?.extensions?.tap_actions?.actions;
|
|
115
|
+
|
|
116
|
+
if (!actions) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const localStorageAction = actions.find(
|
|
121
|
+
(action) => action?.type === "localStorageToggleFlag"
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
if (!localStorageAction?.options?.key) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const keyNamespace = localStorageAction.options.key;
|
|
129
|
+
|
|
130
|
+
const tag = localStorageAction.options?.selector
|
|
131
|
+
? get(item, localStorageAction.options.selector)
|
|
132
|
+
: (item.extensions?.tag ?? item.id);
|
|
133
|
+
|
|
134
|
+
if (!tag) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const multiSelectProvider =
|
|
140
|
+
StorageMultiSelectProvider.getProvider(keyNamespace);
|
|
141
|
+
|
|
142
|
+
const selectedItems = multiSelectProvider.getSelectedItems();
|
|
143
|
+
|
|
144
|
+
return selectedItems.includes(tag);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function getLocalStorageSetPayload(extraProps) {
|
|
151
|
+
const { item } = extraProps;
|
|
152
|
+
|
|
153
|
+
const hasLocalStorageSetAction = item?.extensions?.tap_actions?.actions?.some(
|
|
154
|
+
(action) => action?.type === "localStorageSet"
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
if (!hasLocalStorageSetAction) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
[ANALYTICS_PREFERENCES_EVENTS.ITEM_SELECTED_STATUS]: true,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function getLocalStorageToggleFlagPayload(extraProps) {
|
|
167
|
+
const { item } = extraProps;
|
|
168
|
+
|
|
169
|
+
const hasLocalStorageToggleAction =
|
|
170
|
+
item?.extensions?.tap_actions?.actions?.some(
|
|
171
|
+
(action) => action?.type === "localStorageToggleFlag"
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
if (!hasLocalStorageToggleAction) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const previouslySelected = isItemPreviouslySelected(item);
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
[ANALYTICS_PREFERENCES_EVENTS.ITEM_SELECTED_STATUS]: !previouslySelected,
|
|
182
|
+
[ANALYTICS_PREFERENCES_EVENTS.PREVIOUS_SELECTED_STATE]: previouslySelected,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
105
186
|
export function playEventForType(item) {
|
|
106
187
|
const itemType = item?.type && item.type?.value;
|
|
107
188
|
|
|
@@ -4,7 +4,7 @@ import { postAnalyticEvent } from "../manager";
|
|
|
4
4
|
import { ANALYTICS_CORE_EVENTS } from "../events";
|
|
5
5
|
|
|
6
6
|
type SendHeaderClickEventProps = {
|
|
7
|
-
extraProps:
|
|
7
|
+
extraProps: Record<string, any>;
|
|
8
8
|
component?: ZappUIComponent;
|
|
9
9
|
zappPipesData?: ZappPipesData;
|
|
10
10
|
item?: ZappEntry;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
/// <reference types="../../" />
|
|
1
2
|
import { log_error, log_debug } from "../logger";
|
|
2
3
|
import { replaceAnalyticsPropsNils } from "./helper";
|
|
3
4
|
import { postAnalyticEvent } from "../manager";
|
|
4
5
|
|
|
5
6
|
import { ANALYTICS_CORE_EVENTS } from "../events";
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
type AnalyticsDefaultHelperProperties = {
|
|
8
9
|
analyticsScreenData: AnalyticsScreenProperties;
|
|
9
10
|
extraProps: any;
|
|
10
11
|
props;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { log_error, log_debug } from "../logger";
|
|
2
|
-
|
|
3
|
-
import { ANALYTICS_CORE_EVENTS } from "../events";
|
|
4
|
-
|
|
2
|
+
import { ANALYTICS_CORE_EVENTS, ACTION_TYPE } from "../events";
|
|
5
3
|
import { postAnalyticEvent } from "../manager";
|
|
6
4
|
import {
|
|
7
5
|
replaceAnalyticsPropsNils,
|
|
8
6
|
eventForEntry,
|
|
9
7
|
eventForComponent,
|
|
10
8
|
extensionsEvents,
|
|
9
|
+
getLocalStorageSetPayload,
|
|
10
|
+
getLocalStorageToggleFlagPayload,
|
|
11
11
|
} from "./helper";
|
|
12
12
|
|
|
13
13
|
declare type AnalyticsDefaultHelperProperties = {
|
|
@@ -26,7 +26,16 @@ export const sendOnClickEvent = ({
|
|
|
26
26
|
const castedExtraProps: ExtraProps = extraProps;
|
|
27
27
|
const componentData = component || extraProps.component;
|
|
28
28
|
const data = zappPipesData || extraProps.zappPipesData;
|
|
29
|
-
|
|
29
|
+
|
|
30
|
+
const actionCellPayload =
|
|
31
|
+
extraProps?.item?.type?.value === ACTION_TYPE
|
|
32
|
+
? getLocalStorageSetPayload(extraProps) ||
|
|
33
|
+
getLocalStorageToggleFlagPayload(extraProps)
|
|
34
|
+
: null;
|
|
35
|
+
|
|
36
|
+
const eventName = actionCellPayload
|
|
37
|
+
? ANALYTICS_CORE_EVENTS.TAP_SELECTABLE_CELL
|
|
38
|
+
: ANALYTICS_CORE_EVENTS.TAP_CELL;
|
|
30
39
|
|
|
31
40
|
if (!analyticsScreenData) {
|
|
32
41
|
log_error(
|
|
@@ -44,6 +53,7 @@ export const sendOnClickEvent = ({
|
|
|
44
53
|
...replaceAnalyticsPropsNils({
|
|
45
54
|
...analyticsScreenData,
|
|
46
55
|
}),
|
|
56
|
+
...actionCellPayload,
|
|
47
57
|
};
|
|
48
58
|
|
|
49
59
|
if (analyticsCustomProperties) {
|
|
@@ -3,8 +3,22 @@ import { ANALYTICS_CORE_EVENTS } from "../events";
|
|
|
3
3
|
|
|
4
4
|
jest.mock("@applicaster/zapp-react-native-utils/reactUtils", () => ({
|
|
5
5
|
isWeb: jest.fn(),
|
|
6
|
+
platformSelect: jest.fn(
|
|
7
|
+
(options) => options.android || options.ios || options.web
|
|
8
|
+
),
|
|
6
9
|
}));
|
|
7
10
|
|
|
11
|
+
jest.mock(
|
|
12
|
+
"@applicaster/zapp-react-native-bridge/ZappStorage/StorageMultiSelectProvider",
|
|
13
|
+
() => ({
|
|
14
|
+
StorageMultiSelectProvider: {
|
|
15
|
+
getProvider: jest.fn(() => ({
|
|
16
|
+
getSelectedItems: jest.fn(() => []),
|
|
17
|
+
})),
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
);
|
|
21
|
+
|
|
8
22
|
const mock_postAnalyticEvent = jest.fn();
|
|
9
23
|
const mock_startAnalyticsTimedEvent = jest.fn();
|
|
10
24
|
const mock_endAnalyticsTimedEvent = jest.fn();
|
package/analyticsUtils/events.ts
CHANGED
|
@@ -17,8 +17,11 @@ export const SCREEN_VIEW_EVENTS = {
|
|
|
17
17
|
TIME_ON_SCREEN: "time_on_screen",
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
+
export const ACTION_TYPE = "action";
|
|
21
|
+
|
|
20
22
|
export const TAPPING_EVENTS = {
|
|
21
23
|
TAP_CELL: "tap_cell",
|
|
24
|
+
TAP_SELECTABLE_CELL: "tap_selectable_cell",
|
|
22
25
|
TAP_MENU: "tap_menu",
|
|
23
26
|
TAP_NAVBAR_BACK_BUTTON: "tap_navbar_back_button",
|
|
24
27
|
};
|
|
@@ -99,6 +102,11 @@ export const ANALYTICS_COMPONENT_EVENTS = {
|
|
|
99
102
|
COMPONENT_SOURCE: "component_source",
|
|
100
103
|
};
|
|
101
104
|
|
|
105
|
+
export const ANALYTICS_PREFERENCES_EVENTS = {
|
|
106
|
+
ITEM_SELECTED_STATUS: "item_selected_status",
|
|
107
|
+
PREVIOUS_SELECTED_STATE: "previous_selected_state",
|
|
108
|
+
};
|
|
109
|
+
|
|
102
110
|
// ---------------- EVENTS ---------------------
|
|
103
111
|
export const AD_EVENT = {
|
|
104
112
|
ad_break_start: "player_ad_break_start",
|
package/analyticsUtils/index.tsx
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/// <reference types="@applicaster/zapp-react-native-utils" />
|
|
2
1
|
import * as R from "ramda";
|
|
3
2
|
import * as React from "react";
|
|
4
3
|
import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
@@ -31,7 +30,7 @@ import { ANALYTICS_CORE_EVENTS } from "./events";
|
|
|
31
30
|
import { noop } from "../functionUtils";
|
|
32
31
|
|
|
33
32
|
type ComponentWithChildrenProps = {
|
|
34
|
-
children: React.
|
|
33
|
+
children: React.ReactElement;
|
|
35
34
|
};
|
|
36
35
|
|
|
37
36
|
export function sendSelectCellEvent(item, component, headerTitle, itemIndex) {
|
|
@@ -120,11 +119,11 @@ export function getAnalyticsFunctions({
|
|
|
120
119
|
export const AnalyticsContext =
|
|
121
120
|
React.createContext<GetAnalyticsFunctions>(noop);
|
|
122
121
|
|
|
123
|
-
export function AnalyticsProvider(
|
|
122
|
+
export function AnalyticsProvider({ children }: ComponentWithChildrenProps) {
|
|
124
123
|
return (
|
|
125
124
|
// @ts-ignore - this is a valid context provider
|
|
126
125
|
<AnalyticsContext.Provider value={getAnalyticsFunctions}>
|
|
127
|
-
{
|
|
126
|
+
{children}
|
|
128
127
|
</AnalyticsContext.Provider>
|
|
129
128
|
);
|
|
130
129
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-use-before-define */
|
|
2
2
|
import * as R from "ramda";
|
|
3
3
|
import { NativeModules } from "react-native";
|
|
4
|
-
import { ANALYTICS_CORE_EVENTS } from "
|
|
4
|
+
import { ANALYTICS_CORE_EVENTS } from "./events";
|
|
5
5
|
|
|
6
6
|
import { analyticsUtilsLogger } from "./logger";
|
|
7
7
|
|
|
@@ -65,10 +65,6 @@ export class Hook implements HookInterface {
|
|
|
65
65
|
event: (typeof HOOKS_EVENTS)[keyof typeof HOOKS_EVENTS],
|
|
66
66
|
...args
|
|
67
67
|
) {
|
|
68
|
-
if (this.state === hookState(HOOKS_EVENTS.CANCEL)) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
68
|
this.state = hookState(event);
|
|
73
69
|
this.manager.subscriber.invokeHandler(event, ...args);
|
|
74
70
|
}
|
|
@@ -198,4 +194,8 @@ export class Hook implements HookInterface {
|
|
|
198
194
|
R.eqProps("weight", nextHook, this)
|
|
199
195
|
);
|
|
200
196
|
}
|
|
197
|
+
|
|
198
|
+
isCancelled(): boolean {
|
|
199
|
+
return this.state === hookState(HOOKS_EVENTS.CANCEL);
|
|
200
|
+
}
|
|
201
201
|
}
|
|
@@ -255,7 +255,7 @@ export function HooksManager({
|
|
|
255
255
|
* @param {Array<Hook>} restOfHooks to run
|
|
256
256
|
* @returns {function} callback function
|
|
257
257
|
*/
|
|
258
|
-
function hookCallback(hookPlugin, restOfHooks, initialPayload) {
|
|
258
|
+
function hookCallback(hookPlugin: Hook, restOfHooks: Hook[], initialPayload) {
|
|
259
259
|
/**
|
|
260
260
|
* callback invoked after a hook is executed
|
|
261
261
|
* @param {object} options
|
|
@@ -273,6 +273,16 @@ export function HooksManager({
|
|
|
273
273
|
}) {
|
|
274
274
|
let callback = callbackArg;
|
|
275
275
|
|
|
276
|
+
if (hookPlugin.isCancelled()) {
|
|
277
|
+
logHookEvent(
|
|
278
|
+
hooksManagerLogger.info,
|
|
279
|
+
`hookCallback: hook was cancelled: ${hookPlugin["identifier"]}`,
|
|
280
|
+
{}
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
276
286
|
if (error) {
|
|
277
287
|
logHookEvent(
|
|
278
288
|
hooksManagerLogger.error,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { BehaviorSubject } from "rxjs";
|
|
2
2
|
import { accessibilityManagerLogger as logger } from "./logger";
|
|
3
|
-
import { TTSManager } from "../platform
|
|
3
|
+
import { TTSManager } from "../platform";
|
|
4
4
|
import { BUTTON_ACCESSIBILITY_KEYS } from "./const";
|
|
5
|
+
import { toString } from "../../utils";
|
|
5
6
|
import { AccessibilityRole } from "react-native";
|
|
6
7
|
|
|
7
8
|
export class AccessibilityManager {
|
|
@@ -135,7 +136,9 @@ export class AccessibilityManager {
|
|
|
135
136
|
});
|
|
136
137
|
}
|
|
137
138
|
|
|
138
|
-
public getButtonAccessibilityProps(
|
|
139
|
+
public getButtonAccessibilityProps(name: string): AccessibilityProps {
|
|
140
|
+
const buttonName = toString(name);
|
|
141
|
+
|
|
139
142
|
const buttonConfig = BUTTON_ACCESSIBILITY_KEYS[buttonName];
|
|
140
143
|
|
|
141
144
|
if (!buttonConfig) {
|
|
@@ -144,7 +147,7 @@ export class AccessibilityManager {
|
|
|
144
147
|
accessibilityHint: `Press button to perform action on ${buttonName}`,
|
|
145
148
|
"aria-label": buttonName,
|
|
146
149
|
"aria-description": `Press button to perform action on ${buttonName}`,
|
|
147
|
-
accessibilityRole: "button"
|
|
150
|
+
accessibilityRole: "button",
|
|
148
151
|
"aria-role": "button",
|
|
149
152
|
};
|
|
150
153
|
}
|
|
@@ -163,7 +166,7 @@ export class AccessibilityManager {
|
|
|
163
166
|
accessibilityHint: hint,
|
|
164
167
|
"aria-label": label,
|
|
165
168
|
"aria-description": hint,
|
|
166
|
-
accessibilityRole: "button"
|
|
169
|
+
accessibilityRole: "button",
|
|
167
170
|
"aria-role": "button",
|
|
168
171
|
};
|
|
169
172
|
}
|
|
@@ -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);
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { getArtworkImage } from "..";
|
|
2
|
+
import { DEFAULT_IMAGE } from "../assets";
|
|
3
|
+
|
|
4
|
+
describe("getArtworkImage", () => {
|
|
5
|
+
const entryWithImage = {
|
|
6
|
+
media_group: [
|
|
7
|
+
{
|
|
8
|
+
type: "image",
|
|
9
|
+
media_item: [
|
|
10
|
+
{ key: "artwork_key", src: "image_from_entry" },
|
|
11
|
+
{ key: "other_key", src: "other_image" },
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
extensions: {
|
|
16
|
+
artwork_key: "artwork_key",
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const entryWithoutImage = {
|
|
21
|
+
media_group: [
|
|
22
|
+
{
|
|
23
|
+
type: "image",
|
|
24
|
+
media_item: [{ key: "other_key", src: "other_image" }],
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
extensions: {
|
|
28
|
+
artwork_key: "artwork_key",
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const pluginConfigWithImage = {
|
|
33
|
+
artwork_key: "plugin_artwork_key",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
it("returns image from entry extensions key", () => {
|
|
37
|
+
const result = getArtworkImage({
|
|
38
|
+
key: "artwork_key",
|
|
39
|
+
entry: entryWithImage,
|
|
40
|
+
plugin_configuration: {},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(result).toBe("image_from_entry");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("returns image from plugin configuration key if not in entry", () => {
|
|
47
|
+
const entryNoMatch = {
|
|
48
|
+
...entryWithoutImage,
|
|
49
|
+
extensions: { artwork_key: "not_found_key" },
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const pluginConfig = {
|
|
53
|
+
artwork_key: "plugin_artwork_key",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const entryWithPluginImage = {
|
|
57
|
+
...entryNoMatch,
|
|
58
|
+
media_group: [
|
|
59
|
+
{
|
|
60
|
+
type: "image",
|
|
61
|
+
media_item: [{ key: "plugin_artwork_key", src: "image_from_plugin" }],
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const result = getArtworkImage({
|
|
67
|
+
key: "artwork_key",
|
|
68
|
+
entry: entryWithPluginImage,
|
|
69
|
+
plugin_configuration: pluginConfig,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(result).toBe("image_from_plugin");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("returns DEFAULT_IMAGE if neither entry nor plugin configuration has image", () => {
|
|
76
|
+
const entryNoImage = {
|
|
77
|
+
media_group: [
|
|
78
|
+
{
|
|
79
|
+
type: "image",
|
|
80
|
+
media_item: [{ key: "other_key", src: "other_image" }],
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
extensions: { artwork_key: "not_found_key" },
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const pluginConfig = { artwork_key: "not_found_key" };
|
|
87
|
+
|
|
88
|
+
const result = getArtworkImage({
|
|
89
|
+
key: "artwork_key",
|
|
90
|
+
entry: entryNoImage,
|
|
91
|
+
plugin_configuration: pluginConfig,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(result).toBe(DEFAULT_IMAGE);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("handles undefined key gracefully", () => {
|
|
98
|
+
const result = getArtworkImage({
|
|
99
|
+
key: undefined,
|
|
100
|
+
entry: entryWithImage,
|
|
101
|
+
plugin_configuration: pluginConfigWithImage,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(result).toBe(DEFAULT_IMAGE);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("handles missing entry or plugin_configuration gracefully", () => {
|
|
108
|
+
const result = getArtworkImage({
|
|
109
|
+
key: "artwork_key",
|
|
110
|
+
entry: undefined,
|
|
111
|
+
plugin_configuration: undefined,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(result).toBe(DEFAULT_IMAGE);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("handles empty media_group", () => {
|
|
118
|
+
const entryEmptyMedia = {
|
|
119
|
+
media_group: [],
|
|
120
|
+
extensions: { artwork_key: "artwork_key" },
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const result = getArtworkImage({
|
|
124
|
+
key: "artwork_key",
|
|
125
|
+
entry: entryEmptyMedia,
|
|
126
|
+
plugin_configuration: pluginConfigWithImage,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(result).toBe(DEFAULT_IMAGE);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("handles missing extensions/config keys", () => {
|
|
133
|
+
const entryNoExt = { media_group: [], extensions: {} };
|
|
134
|
+
const pluginConfig = {};
|
|
135
|
+
|
|
136
|
+
const result = getArtworkImage({
|
|
137
|
+
key: "artwork_key",
|
|
138
|
+
entry: entryNoExt,
|
|
139
|
+
plugin_configuration: pluginConfig,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(result).toBe(DEFAULT_IMAGE);
|
|
143
|
+
});
|
|
144
|
+
});
|