@applicaster/zapp-react-native-utils 13.0.0-alpha.2060875348 → 13.0.0-alpha.2082004638
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 +93 -6
- package/appUtils/HooksManager/index.ts +1 -1
- package/appUtils/contextKeysManager/contextResolver.ts +1 -12
- package/appUtils/focusManager/index.ts +1 -10
- package/appUtils/orientationHelper.ts +11 -13
- package/appUtils/playerManager/player.ts +24 -12
- package/appUtils/playerManager/playerNative.ts +18 -3
- package/focusManager/index.ts +9 -1
- package/functionUtils/index.ts +21 -8
- package/manifestUtils/defaultManifestConfigurations/player.js +8 -0
- package/manifestUtils/fonts/index.js +5 -6
- package/numberUtils/index.ts +7 -5
- package/package.json +2 -6
- package/playerUtils/configurationGenerator.ts +8 -0
- package/reactHooks/app/useAppState.ts +2 -2
- package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +5 -7
- package/reactHooks/autoscrolling/useTrackedView.ts +12 -1
- package/reactHooks/feed/useFeedLoader.tsx +4 -2
- package/reactHooks/hookModal/hooks/useHookModalScreenData.ts +39 -0
- package/reactHooks/navigation/__tests__/index.test.tsx +6 -17
- package/reactHooks/navigation/index.ts +6 -4
- package/reactHooks/navigation/useRoute.ts +2 -4
- package/services/js2native.ts +673 -0
- package/time/BackgroundTimer.ts +55 -0
- package/time/Timer.ts +6 -6
- package/zappFrameworkUtils/loginPluginUtils.ts +6 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="@applicaster/applicaster-types" />
|
|
2
2
|
import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
|
|
3
|
-
import React, { createContext, useMemo } from "react";
|
|
3
|
+
import React, { createContext, useEffect, useMemo } from "react";
|
|
4
4
|
import {
|
|
5
5
|
actionExecutor as _actionExecutor,
|
|
6
6
|
ActionResult,
|
|
@@ -19,8 +19,15 @@ import {
|
|
|
19
19
|
EntryResolver,
|
|
20
20
|
resolveObjectValues,
|
|
21
21
|
} from "../appUtils/contextKeysManager/contextResolver";
|
|
22
|
+
import { useNavigation } from "../reactHooks";
|
|
23
|
+
import { get } from "lodash";
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
import {
|
|
26
|
+
useContentTypes,
|
|
27
|
+
usePickFromState,
|
|
28
|
+
} from "@applicaster/zapp-react-native-redux/hooks";
|
|
29
|
+
|
|
30
|
+
export const { log_error, log_info, log_debug } = createLogger({
|
|
24
31
|
subsystem: "ActionExecutorContext",
|
|
25
32
|
category: "General",
|
|
26
33
|
});
|
|
@@ -51,6 +58,22 @@ type Props = {
|
|
|
51
58
|
children: React.ReactNode;
|
|
52
59
|
};
|
|
53
60
|
|
|
61
|
+
function findParentComponent(
|
|
62
|
+
childId: string,
|
|
63
|
+
parent: ZappRiver | ZappUIComponent
|
|
64
|
+
): ZappRiver | ZappUIComponent | null {
|
|
65
|
+
for (const child of parent.ui_components) {
|
|
66
|
+
if (child.id === childId) return parent;
|
|
67
|
+
|
|
68
|
+
if (child.ui_components) {
|
|
69
|
+
const found = findParentComponent(childId, child);
|
|
70
|
+
if (found) return found;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
54
77
|
const prepareDefaultActions = (actionExecutor) => {
|
|
55
78
|
actionExecutor.registerAction("localStorageSet", async (action) => {
|
|
56
79
|
const namespaces = action.options.content;
|
|
@@ -65,11 +88,13 @@ const prepareDefaultActions = (actionExecutor) => {
|
|
|
65
88
|
async (_action, context) => {
|
|
66
89
|
const dispatch = appStore.getDispatch();
|
|
67
90
|
|
|
68
|
-
|
|
91
|
+
const dataSource =
|
|
92
|
+
context?.component?.data?.source ||
|
|
93
|
+
findParentComponent(context?.component.id, context?.screenData)?.data
|
|
94
|
+
?.source;
|
|
69
95
|
|
|
70
|
-
|
|
96
|
+
log_info(`handleAction: refreshComponent for dataSource:${dataSource}`);
|
|
71
97
|
|
|
72
|
-
// TODO: Later we can pass component ids to do refresh instead of current one
|
|
73
98
|
// TODO: In theory we should wait callback to complete, before completing the action, but now it's not needed
|
|
74
99
|
// TODO: handle focused item removal
|
|
75
100
|
dispatch(
|
|
@@ -169,8 +194,29 @@ const prepareDefaultActions = (actionExecutor) => {
|
|
|
169
194
|
action: ActionType,
|
|
170
195
|
context?: Record<string, any>
|
|
171
196
|
): Promise<ActionResult> => {
|
|
197
|
+
if (!context) {
|
|
198
|
+
log_error(
|
|
199
|
+
"handleAction: localStorageToggleFlag action missing context"
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
return ActionResult.Error;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const entry = context?.entry as ZappEntry;
|
|
206
|
+
|
|
207
|
+
if (!entry) {
|
|
208
|
+
log_error(
|
|
209
|
+
"handleAction: localStorageToggleFlag action missing entry. Entry is required to get the tag."
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
return ActionResult.Error;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const tag = action.options?.selector
|
|
216
|
+
? get(entry, action.options.selector)
|
|
217
|
+
: (entry.extensions?.tag ?? entry.id);
|
|
218
|
+
|
|
172
219
|
const keyNamespace = action.options?.key;
|
|
173
|
-
const tag = context?.entry?.extensions?.tag;
|
|
174
220
|
|
|
175
221
|
if (keyNamespace && tag) {
|
|
176
222
|
const multiSelectProvider =
|
|
@@ -211,6 +257,10 @@ export function withActionExecutor(Component) {
|
|
|
211
257
|
prepareDefaultActions(_actionExecutor);
|
|
212
258
|
|
|
213
259
|
return function ActionExecutorComponent(props: Props) {
|
|
260
|
+
const navigator = useNavigation();
|
|
261
|
+
const { rivers } = usePickFromState(["rivers"]);
|
|
262
|
+
const contentTypes = useContentTypes();
|
|
263
|
+
|
|
214
264
|
const handlers = useMemo(() => {
|
|
215
265
|
return {
|
|
216
266
|
unregisterAction: _actionExecutor.unregisterAction,
|
|
@@ -221,6 +271,43 @@ export function withActionExecutor(Component) {
|
|
|
221
271
|
};
|
|
222
272
|
}, []);
|
|
223
273
|
|
|
274
|
+
useEffect(() => {
|
|
275
|
+
return _actionExecutor.registerAction(
|
|
276
|
+
"navigateToScreen",
|
|
277
|
+
async (action: ActionType, _context?: Record<string, any>) => {
|
|
278
|
+
const screenType = action.options?.typeMapping;
|
|
279
|
+
|
|
280
|
+
if (!screenType) {
|
|
281
|
+
log_error("navigateToScreen: typeMapping option is missing");
|
|
282
|
+
|
|
283
|
+
return ActionResult.Error;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const screenId = contentTypes?.[screenType]?.screen_id || null;
|
|
287
|
+
|
|
288
|
+
if (!screenId) {
|
|
289
|
+
log_error(
|
|
290
|
+
`navigateToScreen: can not resolve screen type mapping: ${screenType}`
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
return ActionResult.Error;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const river = rivers[screenId];
|
|
297
|
+
|
|
298
|
+
if (!river) {
|
|
299
|
+
log_error("navigateToScreen: can not resolve river");
|
|
300
|
+
|
|
301
|
+
return ActionResult.Error;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
navigator.replace(river);
|
|
305
|
+
|
|
306
|
+
return ActionResult.Success;
|
|
307
|
+
}
|
|
308
|
+
);
|
|
309
|
+
}, [navigator, rivers, contentTypes]);
|
|
310
|
+
|
|
224
311
|
return (
|
|
225
312
|
<ActionExecutorContext.Provider value={handlers}>
|
|
226
313
|
<Component {...props} />
|
|
@@ -84,7 +84,7 @@ export function HooksManager({
|
|
|
84
84
|
plugins,
|
|
85
85
|
startUpHooksData = {},
|
|
86
86
|
}: HookManagerArgs): HookManager {
|
|
87
|
-
hooksManagerLogger.addContext({ targetScreen });
|
|
87
|
+
hooksManagerLogger.addContext({ targetScreenId: targetScreen.id });
|
|
88
88
|
|
|
89
89
|
function logHookEvent(func, message, data) {
|
|
90
90
|
func({
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
import { createLogger } from "../../logger";
|
|
2
1
|
import { ContextKeysManager } from "./index";
|
|
3
2
|
import * as R from "ramda";
|
|
4
3
|
|
|
5
|
-
const { log_error } = createLogger({
|
|
6
|
-
subsystem: "contextResolver",
|
|
7
|
-
category: "General",
|
|
8
|
-
});
|
|
9
|
-
|
|
10
4
|
interface IResolver {
|
|
11
5
|
resolve: (string) => Promise<string | number | object>;
|
|
12
6
|
}
|
|
@@ -64,12 +58,7 @@ export const resolveObjectValues = async (
|
|
|
64
58
|
return [key, resolvedValue];
|
|
65
59
|
}
|
|
66
60
|
|
|
67
|
-
|
|
68
|
-
key,
|
|
69
|
-
value,
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
return [key, null];
|
|
61
|
+
return [key, value];
|
|
73
62
|
})
|
|
74
63
|
);
|
|
75
64
|
|
|
@@ -334,16 +334,7 @@ export const focusManager = (function () {
|
|
|
334
334
|
return { success: false };
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
-
function treeLoaded(
|
|
338
|
-
logger.log({
|
|
339
|
-
message: "treeLoaded",
|
|
340
|
-
data: {
|
|
341
|
-
val,
|
|
342
|
-
node,
|
|
343
|
-
currentFocusNode,
|
|
344
|
-
},
|
|
345
|
-
});
|
|
346
|
-
}
|
|
337
|
+
function treeLoaded(_val, _node) {}
|
|
347
338
|
|
|
348
339
|
function pressIn() {
|
|
349
340
|
const currentFocusable = getCurrentFocus();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/// <reference types="@types/react-native" />
|
|
2
1
|
import * as ReactNative from "react-native";
|
|
3
|
-
import { isTV, platformSelect } from "../reactUtils";
|
|
4
2
|
import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
|
|
5
|
-
|
|
3
|
+
|
|
4
|
+
import { isTV, platformSelect } from "../reactUtils";
|
|
5
|
+
import { useIsTablet } from "../reactHooks";
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
QUICK_BRICK_EVENTS,
|
|
@@ -121,8 +121,15 @@ export const removeOrientationChangeListener = (listener: any): void => {
|
|
|
121
121
|
*/
|
|
122
122
|
export const isOrientationCompatible = ({
|
|
123
123
|
orientation,
|
|
124
|
-
layoutData: { width, height },
|
|
124
|
+
layoutData: { width, height, isTablet, isTabletPortrait },
|
|
125
125
|
}) => {
|
|
126
|
+
// We count portrait as compatible, because if default in zapp for mobile layout
|
|
127
|
+
// We count landscape as compatible, because if isTabletPortrait is false,
|
|
128
|
+
// it defaults for device
|
|
129
|
+
if (isTablet && !isTabletPortrait) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
|
|
126
133
|
const isLandscape = width > height;
|
|
127
134
|
|
|
128
135
|
if (
|
|
@@ -146,15 +153,6 @@ export const isOrientationCompatible = ({
|
|
|
146
153
|
return true;
|
|
147
154
|
};
|
|
148
155
|
|
|
149
|
-
export const useIsOrientationCompatible = (orientation) => {
|
|
150
|
-
const { height, width } = ReactNative.useWindowDimensions();
|
|
151
|
-
|
|
152
|
-
return isOrientationCompatible({
|
|
153
|
-
orientation,
|
|
154
|
-
layoutData: { width, height },
|
|
155
|
-
});
|
|
156
|
-
};
|
|
157
|
-
|
|
158
156
|
/**
|
|
159
157
|
* Determine final screen orientation based on device type,
|
|
160
158
|
* settings and orientation value provided by screen data settings
|
|
@@ -97,7 +97,7 @@ export class Player {
|
|
|
97
97
|
this.onVideoLoad = this.onVideoLoad.bind(this);
|
|
98
98
|
this.isFullScreenSupported = this.isFullScreenSupported.bind(this);
|
|
99
99
|
this.onPlayerSeekComplete = this.onPlayerSeekComplete.bind(this);
|
|
100
|
-
|
|
100
|
+
this.onAdBreakEnd = this.onAdBreakEnd.bind(this);
|
|
101
101
|
this.entryObservable = null;
|
|
102
102
|
}
|
|
103
103
|
|
|
@@ -201,17 +201,25 @@ export class Player {
|
|
|
201
201
|
adPosition: currentTime,
|
|
202
202
|
};
|
|
203
203
|
} else {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
204
|
+
this.updateContentDuration({ seekableDuration, duration, currentTime });
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
207
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
208
|
+
protected updateContentDuration = ({
|
|
209
|
+
seekableDuration = null,
|
|
210
|
+
duration = null,
|
|
211
|
+
currentTime = null,
|
|
212
|
+
}) => {
|
|
213
|
+
if (!isEmptyOrNil(seekableDuration) && seekableDuration >= 0) {
|
|
214
|
+
this.playerState.seekableDuration = seekableDuration;
|
|
215
|
+
}
|
|
211
216
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
217
|
+
if (!isEmptyOrNil(duration) && duration >= 0) {
|
|
218
|
+
this.playerState.contentDuration = duration;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!isEmptyOrNil(currentTime) && currentTime >= 0) {
|
|
222
|
+
this.playerState.contentPosition = currentTime;
|
|
215
223
|
}
|
|
216
224
|
};
|
|
217
225
|
|
|
@@ -559,7 +567,7 @@ export class Player {
|
|
|
559
567
|
});
|
|
560
568
|
};
|
|
561
569
|
|
|
562
|
-
onAdBreakEnd
|
|
570
|
+
onAdBreakEnd(event) {
|
|
563
571
|
this.playerState.adState = null;
|
|
564
572
|
this.playerState.isInAdBreak = false;
|
|
565
573
|
|
|
@@ -567,7 +575,7 @@ export class Player {
|
|
|
567
575
|
callbackName: SharedPlayerCallBacksKeys.OnAdBreakEnd,
|
|
568
576
|
event,
|
|
569
577
|
});
|
|
570
|
-
}
|
|
578
|
+
}
|
|
571
579
|
|
|
572
580
|
onAdEnd = (event) => {
|
|
573
581
|
this.playerState.adState = null;
|
|
@@ -736,6 +744,10 @@ export class Player {
|
|
|
736
744
|
return null;
|
|
737
745
|
};
|
|
738
746
|
|
|
747
|
+
public hasResumePosition() {
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
|
|
739
751
|
public getConfig = () => this.config;
|
|
740
752
|
|
|
741
753
|
// TODO: replace with enum of supported orientations
|
|
@@ -54,6 +54,10 @@ export class PlayerNative extends Player {
|
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
public hasResumePosition() {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
57
61
|
getContentPosition = () => {
|
|
58
62
|
return this.playerState.contentPosition;
|
|
59
63
|
};
|
|
@@ -221,11 +225,13 @@ export class PlayerNative extends Player {
|
|
|
221
225
|
};
|
|
222
226
|
|
|
223
227
|
onVideoLoad = (event) => {
|
|
224
|
-
|
|
225
|
-
this.seekTo(this.startPosition);
|
|
226
|
-
this.startPosition = null;
|
|
228
|
+
this.updateContentDuration(event);
|
|
227
229
|
|
|
230
|
+
if (this.startPosition && !this.playerState.isInAdBreak) {
|
|
228
231
|
super.onVideoLoad({ ...event, ignoreContinueWatching: true });
|
|
232
|
+
|
|
233
|
+
this.seekTo(this.startPosition);
|
|
234
|
+
this.startPosition = null;
|
|
229
235
|
} else {
|
|
230
236
|
super.onVideoLoad(event);
|
|
231
237
|
}
|
|
@@ -249,6 +255,15 @@ export class PlayerNative extends Player {
|
|
|
249
255
|
}
|
|
250
256
|
};
|
|
251
257
|
|
|
258
|
+
onAdBreakEnd = (event) => {
|
|
259
|
+
super.onAdBreakEnd(event);
|
|
260
|
+
|
|
261
|
+
if (this.startPosition) {
|
|
262
|
+
this.seekTo(this.startPosition);
|
|
263
|
+
this.startPosition = null;
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
|
|
252
267
|
// This function is called when native player used directly without wrapped component,
|
|
253
268
|
// Since event from native comes with wrapped in NativeEvent property
|
|
254
269
|
getNativeViewListener = (): QuickBrickPlayer.SharedPlayerCallBacks => {
|
package/focusManager/index.ts
CHANGED
|
@@ -117,17 +117,25 @@ export const useIsFocusable = (props: Props): void => {
|
|
|
117
117
|
isNilOrEmpty(zappPipesData?.data) ||
|
|
118
118
|
isNilOrEmpty(zappPipesData?.data?.entry);
|
|
119
119
|
|
|
120
|
+
const hasNoUIComponents = isNilOrEmpty(component?.ui_components);
|
|
121
|
+
|
|
120
122
|
const hasDataError = !!zappPipesData?.error;
|
|
121
123
|
|
|
122
124
|
const hasEmptyGroup = shouldShowEmptyGroup(component);
|
|
123
125
|
|
|
124
126
|
const isGallery = component?.component_type === "gallery-qb";
|
|
125
127
|
|
|
128
|
+
const isGroup = component?.component_type === "group-qb";
|
|
129
|
+
|
|
126
130
|
const isHiddenDueToEmptyData =
|
|
127
131
|
!isLoadingData && hasNoData && hideIfDataEmpty && !hasEmptyGroup;
|
|
128
132
|
|
|
133
|
+
const isEmptyGroup = isGroup && hasNoUIComponents;
|
|
134
|
+
|
|
129
135
|
const defaultFocusableLogic =
|
|
130
|
-
componentCellsFocusable &&
|
|
136
|
+
componentCellsFocusable &&
|
|
137
|
+
(!hasNoData || !hideIfDataEmpty) &&
|
|
138
|
+
!isEmptyGroup;
|
|
131
139
|
|
|
132
140
|
React.useEffect(() => {
|
|
133
141
|
if (isAndroidTVPlatform()) {
|
package/functionUtils/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as R from "ramda";
|
|
2
|
-
import { useRef } from "react";
|
|
2
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
*
|
|
@@ -9,31 +9,44 @@ import { useRef } from "react";
|
|
|
9
9
|
*/
|
|
10
10
|
export const useThrottle = (callback: () => void, delay: number) => {
|
|
11
11
|
const lastCallRef = useRef<number>(0);
|
|
12
|
-
const timeoutRef = useRef<
|
|
12
|
+
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
13
|
+
const callbackRef = useRef<() => void>(callback);
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
callbackRef.current = callback;
|
|
17
|
+
}, [callback]);
|
|
18
|
+
|
|
19
|
+
const throttledFunction = useCallback(() => {
|
|
15
20
|
const now = Date.now();
|
|
16
21
|
|
|
17
22
|
if (now - lastCallRef.current >= delay) {
|
|
18
|
-
|
|
23
|
+
callbackRef.current();
|
|
19
24
|
lastCallRef.current = now;
|
|
20
25
|
} else if (!timeoutRef.current) {
|
|
21
26
|
const remainingTime = delay - (now - lastCallRef.current);
|
|
22
27
|
|
|
23
28
|
timeoutRef.current = setTimeout(() => {
|
|
24
|
-
|
|
29
|
+
callbackRef.current();
|
|
25
30
|
lastCallRef.current = Date.now();
|
|
26
31
|
timeoutRef.current = null;
|
|
27
32
|
}, remainingTime);
|
|
28
33
|
}
|
|
29
|
-
};
|
|
34
|
+
}, [delay]);
|
|
30
35
|
|
|
31
|
-
const cancel = () => {
|
|
36
|
+
const cancel = useCallback(() => {
|
|
32
37
|
if (timeoutRef.current) {
|
|
33
38
|
clearTimeout(timeoutRef.current);
|
|
34
39
|
timeoutRef.current = null;
|
|
35
40
|
}
|
|
36
|
-
};
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
return () => {
|
|
45
|
+
if (timeoutRef.current) {
|
|
46
|
+
clearTimeout(timeoutRef.current);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}, []);
|
|
37
50
|
|
|
38
51
|
return { throttledFunction, cancel };
|
|
39
52
|
};
|
|
@@ -50,6 +50,14 @@ function getPlayerConfiguration({ platform }) {
|
|
|
50
50
|
label_tooltip:
|
|
51
51
|
"Set the query params which control the size of the image e.g width, height. This will help to optimize the loading time, when magic background is enabled.",
|
|
52
52
|
},
|
|
53
|
+
{
|
|
54
|
+
key: "audio_player_background_image_default_color",
|
|
55
|
+
label: "Audio Player Background Image Default Color",
|
|
56
|
+
initial_value: "",
|
|
57
|
+
type: "text_input",
|
|
58
|
+
label_tooltip:
|
|
59
|
+
"Set the default color for the audio player background image if not image or set of gradient colors are not provided.",
|
|
60
|
+
},
|
|
53
61
|
],
|
|
54
62
|
};
|
|
55
63
|
|
|
@@ -224,12 +224,11 @@ function fontKey(label, defaults, conditionalField = null) {
|
|
|
224
224
|
return fields;
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
function tvMenuLabel(label, description, defaults) {
|
|
228
|
-
return fieldsGroup(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
);
|
|
227
|
+
function tvMenuLabel(label, description, defaults, fields = []) {
|
|
228
|
+
return fieldsGroup(label, description, [
|
|
229
|
+
...fields,
|
|
230
|
+
...generateFieldsFromDefaults(label, defaults, TV_MENU_LABEL_FIELDS),
|
|
231
|
+
]);
|
|
233
232
|
}
|
|
234
233
|
|
|
235
234
|
function mobileCellLabel(label, description, defaults, groupFolded) {
|
package/numberUtils/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { logger } from "@applicaster/zapp-react-native-utils/logger";
|
|
|
3
3
|
|
|
4
4
|
const numberUtilsLogger = logger?.addSubsystem("NumberUtils");
|
|
5
5
|
|
|
6
|
-
export const toNumber = (value:
|
|
6
|
+
export const toNumber = (value: unknown): number | undefined => {
|
|
7
7
|
if (R.isNil(value) || R.isEmpty(value)) {
|
|
8
8
|
return undefined;
|
|
9
9
|
}
|
|
@@ -17,10 +17,12 @@ export const toNumber = (value: any): number | undefined => {
|
|
|
17
17
|
return undefined;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
export const toNumberWithDefault = (
|
|
21
|
-
|
|
20
|
+
export const toNumberWithDefault = (
|
|
21
|
+
defaultValue: number,
|
|
22
|
+
value: unknown
|
|
23
|
+
): number => toNumber(value) ?? defaultValue;
|
|
22
24
|
|
|
23
|
-
export const toNumberWithDefaultZero = (value:
|
|
25
|
+
export const toNumberWithDefaultZero = (value: unknown): number =>
|
|
24
26
|
toNumberWithDefault(0, value);
|
|
25
27
|
|
|
26
28
|
export const requireNumber = (obj, key: string) => {
|
|
@@ -39,7 +41,7 @@ export const requireNumber = (obj, key: string) => {
|
|
|
39
41
|
|
|
40
42
|
export const toFiniteNumberWithDefault = (
|
|
41
43
|
defaultValue: number,
|
|
42
|
-
value:
|
|
44
|
+
value: unknown
|
|
43
45
|
): number => {
|
|
44
46
|
const possibleNumber = toNumberWithDefault(defaultValue, value);
|
|
45
47
|
|
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-utils",
|
|
3
|
-
"version": "13.0.0-alpha.
|
|
3
|
+
"version": "13.0.0-alpha.2082004638",
|
|
4
4
|
"description": "Applicaster Zapp React Native utilities package",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"test": "jest -w=2",
|
|
9
8
|
"build": "echo skipping hybrid ts build in main packages for now"
|
|
10
9
|
},
|
|
11
10
|
"publishConfig": {
|
|
@@ -28,7 +27,7 @@
|
|
|
28
27
|
},
|
|
29
28
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
30
29
|
"dependencies": {
|
|
31
|
-
"@applicaster/applicaster-types": "13.0.0-alpha.
|
|
30
|
+
"@applicaster/applicaster-types": "13.0.0-alpha.2082004638",
|
|
32
31
|
"buffer": "^5.2.1",
|
|
33
32
|
"camelize": "^1.0.0",
|
|
34
33
|
"dayjs": "^1.11.10",
|
|
@@ -45,8 +44,5 @@
|
|
|
45
44
|
"uglify-js": "*",
|
|
46
45
|
"validate-color": "*",
|
|
47
46
|
"zustand": "*"
|
|
48
|
-
},
|
|
49
|
-
"devDependencies": {
|
|
50
|
-
"@types/react-native": "0.69.6"
|
|
51
47
|
}
|
|
52
48
|
}
|
|
@@ -55,6 +55,14 @@ function getPlayerConfiguration({ platform }) {
|
|
|
55
55
|
label_tooltip:
|
|
56
56
|
"Set the query params which control the size of the image e.g width, height. This will help to optimize the loading time, when magic background is enabled.",
|
|
57
57
|
},
|
|
58
|
+
{
|
|
59
|
+
key: "audio_player_background_image_default_color",
|
|
60
|
+
label: "Audio Player Background Image Default Color",
|
|
61
|
+
initial_value: "",
|
|
62
|
+
type: "text_input",
|
|
63
|
+
label_tooltip:
|
|
64
|
+
"Set the default color for the audio player background image if not image or set of gradient colors are not provided.",
|
|
65
|
+
},
|
|
58
66
|
],
|
|
59
67
|
};
|
|
60
68
|
|
|
@@ -14,10 +14,10 @@ export function useAppState(returnIsActive) {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
React.useEffect(() => {
|
|
17
|
-
|
|
17
|
+
AppState.addEventListener("change", setAppState);
|
|
18
18
|
|
|
19
19
|
return () => {
|
|
20
|
-
|
|
20
|
+
AppState.removeEventListener("change", setAppState);
|
|
21
21
|
};
|
|
22
22
|
}, []);
|
|
23
23
|
|
|
@@ -35,25 +35,23 @@ const Wrapper = ({ children }: { children: React.ReactChild }) => (
|
|
|
35
35
|
const { useTrackedView } = require("../useTrackedView");
|
|
36
36
|
|
|
37
37
|
describe("useTrackCurrentAutoScrollingElement", () => {
|
|
38
|
-
it("should update position for selected component",
|
|
38
|
+
it("should update position for selected component - onViewportEnter", () => {
|
|
39
39
|
const { result } = renderHook(() => useTrackedView("123"), {
|
|
40
40
|
wrapper: Wrapper,
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
// Fast-forward until all timers have been executed
|
|
44
|
-
jest.advanceTimersByTime(3000);
|
|
45
|
-
|
|
46
43
|
expect(result.current.inViewPort).toBe(false);
|
|
47
44
|
|
|
48
45
|
const mockRect = {
|
|
49
46
|
rect: { left: 1, right: 1, top: 1, bottom: 1 },
|
|
50
47
|
};
|
|
51
48
|
|
|
52
|
-
act(() => {
|
|
53
|
-
result.current.onPositionUpdated(mockRect);
|
|
49
|
+
act(async () => {
|
|
50
|
+
await result.current.onPositionUpdated(mockRect);
|
|
54
51
|
});
|
|
55
52
|
|
|
56
|
-
|
|
53
|
+
// Fast-forward until all timers have been executed
|
|
54
|
+
jest.runAllTimers();
|
|
57
55
|
|
|
58
56
|
expect(result.current.inViewPort).toBe(true);
|
|
59
57
|
|
|
@@ -2,6 +2,10 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
|
|
2
2
|
import { useScreenTrackedViewPositionsContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenTrackedViewPositionsContext";
|
|
3
3
|
import { useDimensions } from "@applicaster/zapp-react-native-utils/reactHooks/layout";
|
|
4
4
|
import { findTrackedView } from "./utils/findTrackedView";
|
|
5
|
+
import { isNil } from "ramda";
|
|
6
|
+
import { withTimeout } from "@applicaster/zapp-react-native-utils/idleUtils";
|
|
7
|
+
|
|
8
|
+
const TIMEOUT_FOR_DELAY_ON_VIEWPORT_ENTER_EVENT = 500; // ms
|
|
5
9
|
|
|
6
10
|
const getNormalizedRect = (rect, componentId, screenWidth, screenHeight) => {
|
|
7
11
|
if (!rect) {
|
|
@@ -37,9 +41,16 @@ export const useTrackedView = (componentId: string) => {
|
|
|
37
41
|
);
|
|
38
42
|
|
|
39
43
|
const onPositionUpdated = useCallback(
|
|
40
|
-
({ rect }) => {
|
|
44
|
+
async ({ rect }) => {
|
|
41
45
|
const currentPosition = normalizedRect(componentId, rect);
|
|
42
46
|
|
|
47
|
+
// There is an issue on the native side where they are unable to guarantee
|
|
48
|
+
// that the order of onViewportLeave and onViewportEnter events is always correct.
|
|
49
|
+
// This workaround/hack delays the onViewportEnter event to ensure it is received last.
|
|
50
|
+
if (!isNil(currentPosition)) {
|
|
51
|
+
await withTimeout(TIMEOUT_FOR_DELAY_ON_VIEWPORT_ENTER_EVENT);
|
|
52
|
+
}
|
|
53
|
+
|
|
43
54
|
updateComponentsPositions(componentId, currentPosition as any);
|
|
44
55
|
|
|
45
56
|
setInViewPort(!!currentPosition);
|
|
@@ -5,7 +5,7 @@ import { loadPipesData } from "@applicaster/zapp-react-native-redux/ZappPipes";
|
|
|
5
5
|
import { useZappPipesFeed } from "@applicaster/zapp-react-native-redux/hooks";
|
|
6
6
|
|
|
7
7
|
import { reactHooksLogger } from "../logger";
|
|
8
|
-
import {
|
|
8
|
+
import { shouldDispatchData, useIsInitialRender } from "../utils";
|
|
9
9
|
import { useInflatedUrl } from "./useInflatedUrl";
|
|
10
10
|
import { useRoute } from "../navigation";
|
|
11
11
|
|
|
@@ -55,7 +55,9 @@ export const useFeedLoader = ({
|
|
|
55
55
|
|
|
56
56
|
const currentFeed = useZappPipesFeed(callableFeedUrl);
|
|
57
57
|
|
|
58
|
-
const riverId =
|
|
58
|
+
const riverId =
|
|
59
|
+
(screenData as LegacyNavigationScreenData)?.targetScreen?.id ??
|
|
60
|
+
screenData?.id;
|
|
59
61
|
|
|
60
62
|
const reloadData = React.useCallback<ReloadDataFunction>(
|
|
61
63
|
(silentRefresh = true, callback) => {
|