@applicaster/zapp-react-native-utils 12.0.0-alpha.5882013252 → 12.0.0-alpha.5895113034

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.
Files changed (93) hide show
  1. package/actionsExecutor/ActionExecutorContext.tsx +16 -8
  2. package/analyticsUtils/AnalyticPlayerListener.ts +155 -0
  3. package/analyticsUtils/AnalyticsEvents/index.ts +6 -0
  4. package/analyticsUtils/AnalyticsEvents/searchEvents.ts +21 -0
  5. package/analyticsUtils/PlayerAnalyticsManager.ts +90 -0
  6. package/analyticsUtils/__tests__/analyticsManager.test.js +75 -73
  7. package/analyticsUtils/analyticsPlugins.ts +25 -0
  8. package/analyticsUtils/events.ts +6 -0
  9. package/analyticsUtils/helpers/hooks.ts +1 -1
  10. package/analyticsUtils/index.tsx +6 -0
  11. package/analyticsUtils/{analyticsTracker.ts → playerAnalyticsTracker.ts} +19 -10
  12. package/appUtils/HooksManager/index.js +3 -1
  13. package/appUtils/focusManager/treeDataStructure/Utils/index.js +12 -3
  14. package/appUtils/keyCodes/__tests__/__snapshots__/keyCodes.test.js.snap +1 -1
  15. package/appUtils/keyCodes/__tests__/keyCodes.test.js +80 -85
  16. package/appUtils/keyCodes/keys.web.js +1 -0
  17. package/appUtils/orientationHelper.ts +1 -1
  18. package/appUtils/playerManager/DelayedSeekPlayerWrapper.tsx +179 -0
  19. package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +53 -0
  20. package/appUtils/playerManager/conts.ts +5 -0
  21. package/appUtils/playerManager/index.ts +9 -27
  22. package/appUtils/playerManager/player.ts +60 -9
  23. package/appUtils/playerManager/playerFactory.ts +15 -1
  24. package/appUtils/playerManager/playerNative.ts +39 -3
  25. package/appUtils/playerManager/usePlayerControllerSetup.tsx +23 -4
  26. package/appUtils/playerManager/usePlayerState.tsx +13 -1
  27. package/appUtils/playerManager/usePlayerTitleSummaryOverlay.tsx +50 -0
  28. package/cellUtils/__tests__/cellUtils.test.ts +48 -0
  29. package/cellUtils/__tests__/fixtures.ts +13 -0
  30. package/cellUtils/index.ts +40 -2
  31. package/dateUtils/index.ts +11 -0
  32. package/focusManager/index.ts +1 -1
  33. package/functionUtils/__tests__/__snapshots__/functionUtils.test.js.snap +2 -2
  34. package/functionUtils/__tests__/functionUtils.test.js +244 -248
  35. package/headersUtils/index.ts +1 -0
  36. package/inflatedUrlUtils/index.ts +4 -4
  37. package/localizationUtils/__tests__/localizationUtils.test.ts +78 -55
  38. package/localizationUtils/index.ts +13 -19
  39. package/logger/index.ts +1 -1
  40. package/manifestUtils/defaultManifestConfigurations/generalContent.js +6 -0
  41. package/manifestUtils/defaultManifestConfigurations/player.js +10 -1
  42. package/manifestUtils/index.js +5 -1
  43. package/manifestUtils/secondaryImage/index.js +18 -15
  44. package/modalState/index.ts +57 -0
  45. package/navigationUtils/index.ts +56 -7
  46. package/numberUtils/index.ts +4 -0
  47. package/package.json +3 -2
  48. package/playerUtils/configurationGenerator.ts +11 -1
  49. package/pluginUtils/index.ts +11 -7
  50. package/reactHooks/autoscrolling/__tests__/useTrackCurrentAutoScrollingElement.test.ts +2 -0
  51. package/reactHooks/autoscrolling/__tests__/{useTrackedView.test.ts → useTrackedView.test.tsx} +18 -1
  52. package/reactHooks/cell-click/__tests__/index.test.js +11 -2
  53. package/reactHooks/cell-click/index.ts +4 -4
  54. package/reactHooks/device/index.ts +0 -1
  55. package/reactHooks/device/useIsTablet.tsx +28 -3
  56. package/reactHooks/device/useMemoizedIsTablet.ts +1 -1
  57. package/reactHooks/device/useOrientation.tsx +3 -1
  58. package/reactHooks/feed/__tests__/useInflatedUrl.test.ts +18 -3
  59. package/reactHooks/feed/useInflatedUrl.ts +44 -15
  60. package/reactHooks/flatList/useLoadNextPageIfNeeded.ts +16 -9
  61. package/reactHooks/index.ts +2 -0
  62. package/reactHooks/layout/__tests__/{index.test.js → index.test.tsx} +66 -59
  63. package/reactHooks/layout/getDeviceInfo/index.ts +4 -2
  64. package/reactHooks/layout/index.ts +2 -1
  65. package/reactHooks/layout/types/index.ts +2 -0
  66. package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.native.test.js +11 -16
  67. package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.test.js +12 -14
  68. package/reactHooks/layout/useDimensions/useDimensions.ts +13 -7
  69. package/reactHooks/navigation/__mocks__/index.ts +15 -0
  70. package/reactHooks/navigation/__mocks__/useNavigation.ts +7 -0
  71. package/reactHooks/navigation/__tests__/index.test.tsx +240 -81
  72. package/reactHooks/navigation/index.ts +208 -86
  73. package/reactHooks/player/__tests__/useAutoSeek.test.tsx +8 -1
  74. package/reactHooks/player/useAutoSeek.tsx +28 -8
  75. package/reactHooks/player/useSeek.tsx +4 -2
  76. package/reactHooks/player/useTapSeek.tsx +34 -7
  77. package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +3 -1
  78. package/reactHooks/screen/__mocks__/useScreenContext.ts +3 -1
  79. package/reactHooks/screen/index.ts +2 -0
  80. package/reactHooks/screen/useScreenContext.ts +5 -4
  81. package/reactHooks/screen/useScreenData.ts +1 -1
  82. package/reactHooks/screen/useTargetScreenData.ts +7 -0
  83. package/reactHooks/state/useRivers.ts +3 -1
  84. package/reactHooks/useListenEventBusEvent.ts +1 -4
  85. package/reactHooks/videoModal/hooks/useVideoModalScreenData.tsx +10 -9
  86. package/riverComponetsMeasurementProvider/index.tsx +2 -2
  87. package/stringUtils/index.ts +5 -0
  88. package/testUtils/getByTestId.ts +14 -0
  89. package/testUtils/index.tsx +38 -23
  90. package/types.ts +4 -0
  91. package/reactHooks/device/useIsTablet.android.tsx +0 -14
  92. package/reactHooks/device/useIsTablet.ios.tsx +0 -7
  93. package/reactHooks/device/useIsTablet.web.tsx +0 -3
@@ -1,9 +1,10 @@
1
1
  /* eslint-disable @typescript-eslint/no-use-before-define */
2
2
  /* eslint max-len: off */
3
3
 
4
- import React, { useEffect, useMemo } from "react";
4
+ import React, { useContext, useEffect, useMemo } from "react";
5
5
  import { BackHandler, InteractionManager, NativeModules } from "react-native";
6
6
  import { useSafeAreaInsets } from "react-native-safe-area-context";
7
+ import { isEmpty } from "ramda";
7
8
 
8
9
  import {
9
10
  useContentTypes,
@@ -17,18 +18,104 @@ import { useZappHooksModalState } from "@applicaster/zapp-react-dom-ui-component
17
18
  import { ZappHookModalContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
18
19
  import { PathnameContext } from "@applicaster/zapp-react-native-ui-components/Contexts/PathnameContext";
19
20
  import { HookModalContextT } from "@applicaster/zapp-react-native-ui-components/Contexts/ZappHookModalContext";
20
- import { getNavigationProps } from "@applicaster/zapp-react-native-utils/navigationUtils";
21
21
  import { HOOKS_EVENTS } from "../../appUtils/HooksManager/constants";
22
- import { getRiverFromRoute, getTargetRoute } from "../../navigationUtils";
22
+ import {
23
+ getNavigationProps,
24
+ getRiverFromRoute,
25
+ getTargetRoute,
26
+ } from "../../navigationUtils";
23
27
  import { platformSelect } from "../../reactUtils";
24
28
  import { useConnectionInfo } from "../connection";
25
29
  import { getTargetScreenData } from "../screen/useTargetScreenData";
26
- import { useNavbarState } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
27
30
 
31
+ import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
32
+ import { useNavbarState } from "../screen";
28
33
  import { useNavigation } from "./useNavigation";
34
+ import { useModalStore } from "../../modalState";
35
+ import { ScreenDataContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenDataContext";
29
36
 
30
37
  export { useNavigation } from "./useNavigation";
31
38
 
39
+ /**
40
+ * This function helps to decide wether the navbar should be presented on the screen
41
+ * based on route and / or screen Data
42
+ *
43
+ * Currently the scenarios include
44
+ * - players => false
45
+ * - search screen plugin => false
46
+ * - hooks with `showNavBar: true` or `presentFullScreen` not set to true, defaults to no navbar for hook screens
47
+ * - screens with `allow_screen_plugin_presentation` in general property => false
48
+ *
49
+ * @param {String} route current route of the screen
50
+ * @param {Object} screenData payload associated with the currently presented screen
51
+ * @returns {Boolean}
52
+ */
53
+ export function isNavBarVisible(
54
+ route: string,
55
+ screenData: ZappRiver = {} as ZappRiver,
56
+ showNavBar: boolean,
57
+ canGoBack: boolean,
58
+ videoModalState: QuickBrickVideoModalState | null = null
59
+ ) {
60
+ /* screenData is not actual navigator.data
61
+ * or navigator.screenData,
62
+ * but navigator.data.screen */
63
+ if (route.includes("playable")) {
64
+ return false;
65
+ }
66
+
67
+ if (
68
+ videoModalState?.mode === "FULLSCREEN" &&
69
+ videoModalState?.visible === true
70
+ ) {
71
+ return false;
72
+ }
73
+
74
+ if (screenData?.type === "qb_search_screen" && !isTV()) {
75
+ return false;
76
+ }
77
+
78
+ /* Match if screen is Hook */
79
+ if (route.startsWith("/hooks/")) {
80
+ const module = screenData?.module;
81
+
82
+ if (module?.presentFullScreen) {
83
+ return false;
84
+ }
85
+
86
+ return module?.showNavBar ?? false;
87
+ }
88
+
89
+ if (screenData?.hookPlugin) {
90
+ const hookPlugin = screenData?.hookPlugin?.module;
91
+
92
+ return (
93
+ hookPlugin?.showNavBar || hookPlugin?.presentFullScreen !== true || false
94
+ );
95
+ }
96
+
97
+ if (
98
+ screenData?.general?.allow_screen_plugin_presentation ||
99
+ screenData?.general?.hide_app_nav_bar
100
+ ) {
101
+ return false;
102
+ }
103
+
104
+ const hideSecondaryLevel = screenData?.navigations?.some(
105
+ (navigation) => navigation.styles?.hide_secondary_level_menu
106
+ );
107
+
108
+ if (canGoBack && hideSecondaryLevel) {
109
+ return false;
110
+ }
111
+
112
+ if (typeof showNavBar === "boolean" && !showNavBar) {
113
+ return false;
114
+ }
115
+
116
+ return true;
117
+ }
118
+
32
119
  const { ProfilerBridge } = NativeModules;
33
120
 
34
121
  export const usePathname = () => React.useContext(PathnameContext);
@@ -43,6 +130,91 @@ export const useBackHandler = (cb: () => boolean) => {
43
130
  }, [cb]);
44
131
  };
45
132
 
133
+ // starts with modal/
134
+ const isModalPathname = (pathname: string) => /^modal\//.test(pathname);
135
+
136
+ const isVideoModalPathname = (pathname: string) =>
137
+ /^video-modal\//.test(pathname);
138
+
139
+ const isHookModalPathname = (pathname: string) =>
140
+ /^hooks-modal\//.test(pathname);
141
+
142
+ const isHookPathname = (pathname: string) => /^\/hooks\//.test(pathname);
143
+
144
+ type VariousScreenData = LegacyNavigationScreenData | ZappRiver | ZappEntry;
145
+
146
+ export const useRoute = (): {
147
+ screenData: VariousScreenData;
148
+ pathname: string;
149
+ } => {
150
+ const pathname = usePathname() || "";
151
+ const navigator = useNavigation();
152
+ const screenDataContext = legacyScreenData(useContext(ScreenDataContext));
153
+
154
+ const { plugins, contentTypes, rivers } = usePickFromState([
155
+ "plugins",
156
+ "rivers",
157
+ "contentTypes",
158
+ ]);
159
+
160
+ const { modalState } = useModalStore();
161
+ const hookModalState = useZappHooksModalState();
162
+
163
+ const modalScreenData = modalState.screen;
164
+
165
+ const hookModalScreenData = hookModalState.state.screenData?.payload;
166
+
167
+ const videoModalScreenData =
168
+ navigator.videoModalState?.item &&
169
+ legacyScreenData(
170
+ {
171
+ entry: navigator.videoModalState?.item as ZappEntry,
172
+ screen: getTargetScreenData(
173
+ navigator.videoModalState?.item as ZappEntry,
174
+ rivers,
175
+ contentTypes
176
+ ),
177
+ },
178
+ plugins
179
+ );
180
+
181
+ // There are 4 route scenarios
182
+ // For regular screens take date from navigator stack.
183
+ // is path is model grab screenData from modal state
184
+ // if path is video modal grab screenData from video state
185
+ // if path is hook modal grab screenData from hook state
186
+ // if path is hook grab screenData from screenData
187
+
188
+ if (isModalPathname(pathname)) {
189
+ const screenData = modalScreenData ?? ({} as ZappEntry);
190
+
191
+ return { screenData, pathname };
192
+ }
193
+
194
+ if (isVideoModalPathname(pathname)) {
195
+ const screenData = videoModalScreenData as LegacyNavigationScreenData;
196
+
197
+ return { screenData, pathname };
198
+ }
199
+
200
+ if (isHookModalPathname(pathname)) {
201
+ const screenData = hookModalScreenData;
202
+
203
+ return { screenData, pathname };
204
+ }
205
+
206
+ if (isHookPathname(pathname)) {
207
+ return { screenData: screenDataContext, pathname };
208
+ }
209
+
210
+ // Cover cases where screenContext is used before navigation is initialized
211
+ const screenData = isEmpty(screenDataContext)
212
+ ? navigator.screenData
213
+ : screenDataContext;
214
+
215
+ return { screenData, pathname };
216
+ };
217
+
46
218
  type Callbacks = Partial<{
47
219
  handleHookPresent: ({
48
220
  route,
@@ -209,7 +381,7 @@ export const useRunIfLongPress = (
209
381
  React.useRef<Nullable<ReturnType<typeof setTimeout>>>(null);
210
382
 
211
383
  const resetTimeout = () => {
212
- clearTimeout(pressTimeout.current);
384
+ clearTimeout(pressTimeout.current as ReturnType<typeof setTimeout>);
213
385
  };
214
386
 
215
387
  const startTimeout = (cb: () => void) => {
@@ -231,82 +403,12 @@ export const useRunIfLongPress = (
231
403
  * @returns boolean - navbar visiblity status
232
404
  */
233
405
  export const useIsNavBarVisible = (): boolean => {
234
- const visible = useNavbarState().visible;
406
+ const { visible } = useNavbarState();
235
407
 
236
408
  return useMemo(() => visible, [visible]);
237
409
  };
238
410
 
239
- const isModalPathname = (pathname: string) => /^modal\//.test(pathname);
240
-
241
- const isVideoModalPathname = (pathname: string) =>
242
- /^video-modal\//.test(pathname);
243
-
244
- const isHookModalPathname = (pathname: string) =>
245
- /^hooks-modal\//.test(pathname);
246
-
247
- export const useRoute = () => {
248
- const pathname = usePathname() || "";
249
- const navigator = useNavigation();
250
-
251
- const { plugins, contentTypes, rivers } = usePickFromState([
252
- "plugins",
253
- "rivers",
254
- "contentTypes",
255
- ]);
256
-
257
- const currentStack = navigator.getStackForPathname(pathname);
258
- const modalState = navigator.modalState;
259
- const hookModalState = useZappHooksModalState();
260
-
261
- const modalScreenData = modalState.screen;
262
-
263
- const hookModalScreenData = hookModalState.state.screenData?.payload;
264
-
265
- const videoModalScreenData =
266
- navigator.videoModalState?.item &&
267
- legacyScreenData(
268
- {
269
- entry: navigator.videoModalState?.item,
270
- screen: getTargetScreenData(
271
- navigator.videoModalState?.item,
272
- rivers,
273
- contentTypes
274
- ),
275
- },
276
- plugins
277
- );
278
-
279
- const currentRouteScreenData = legacyScreenData(
280
- {
281
- screen:
282
- currentStack?.state?.screen ??
283
- navigator.data?.screen ??
284
- ({} as ZappRiver),
285
- entry: currentStack?.state?.entry ?? ({} as ZappEntry),
286
- },
287
-
288
- plugins
289
- );
290
-
291
- // There are 4 route scenarios
292
- // For regular screens take date from navigator stack.
293
- // is path is model grab screenData from modal state
294
- // if path is video modal grab screenData from video state
295
- // if path is hook modal grab screenData from hook state
296
-
297
- const screenData = isModalPathname(pathname)
298
- ? modalScreenData
299
- : isVideoModalPathname(pathname)
300
- ? videoModalScreenData
301
- : isHookModalPathname(pathname)
302
- ? hookModalScreenData
303
- : currentRouteScreenData;
304
-
305
- return { screenData, pathname };
306
- };
307
-
308
- export const useGetBottomTabBarHeight = (): number => {
309
- const insets = useSafeAreaInsets();
411
+ const useGetNavBarTopBorderWidth = (): number => {
310
412
  const navigator = useNavigation();
311
413
 
312
414
  const navProps = getNavigationProps({
@@ -315,18 +417,38 @@ export const useGetBottomTabBarHeight = (): number => {
315
417
  category: "menu",
316
418
  });
317
419
 
318
- const navType = useNavigationType();
319
- const isBottomBarNavigation = navType === MenuTypes.bottomTabBar;
320
-
321
- const TAB_BAR_HEIGHT_IOS = 49;
322
- const TAB_BAR_HEIGHT_ANDROID = 56;
420
+ return Number(((navProps?.styles as any)?.top_border_width as number) ?? 0);
421
+ };
323
422
 
324
- if (!isBottomBarNavigation) return 0;
423
+ const TAB_BAR_HEIGHT_IOS = 49;
424
+ const TAB_BAR_HEIGHT_ANDROID = 56;
325
425
 
326
- const tabBarHeight = platformSelect({
327
- ios: TAB_BAR_HEIGHT_IOS + insets.bottom,
426
+ const useGetTabBarHeight = () =>
427
+ platformSelect({
428
+ ios: TAB_BAR_HEIGHT_IOS + (useSafeAreaInsets().bottom ?? 0),
328
429
  android: TAB_BAR_HEIGHT_ANDROID,
329
430
  }) as number;
330
431
 
331
- return tabBarHeight + Number(navProps?.styles?.top_border_width ?? 0);
432
+ export const useGetBottomTabBarHeight = (): number => {
433
+ const topBorderWidth = useGetNavBarTopBorderWidth();
434
+ const tabBarHeight = useGetTabBarHeight();
435
+
436
+ const isBottomBarNavigation = useNavigationType() === MenuTypes.bottomTabBar;
437
+
438
+ return !isBottomBarNavigation ? 0 : tabBarHeight + topBorderWidth;
439
+ };
440
+
441
+ // If current screen is active/focused (visible to the user)
442
+ export const useIsScreenActive = () => {
443
+ const pathname = usePathname();
444
+ const { currentRoute, videoModalState } = useNavigation();
445
+
446
+ if (
447
+ videoModalState.visible &&
448
+ ["FULLSCREEN", "MAXIMIZED", "PIP"].includes(videoModalState.mode)
449
+ ) {
450
+ return false;
451
+ }
452
+
453
+ return pathname === currentRoute;
332
454
  };
@@ -9,11 +9,18 @@ jest.useFakeTimers();
9
9
 
10
10
  const rewind = jest.fn();
11
11
  const forward = jest.fn();
12
+ const exitDelayedSeek = jest.fn();
13
+ const enterDelayedSeek = jest.fn();
12
14
 
13
15
  jest
14
16
  .spyOn(playerManager, "getInstanceController")
15
17
  // @ts-ignore
16
- .mockImplementation(() => ({ rewind, forward }));
18
+ .mockImplementation(() => ({
19
+ rewind,
20
+ forward,
21
+ exitDelayedSeek,
22
+ enterDelayedSeek,
23
+ }));
17
24
 
18
25
  describe("useAutoSeek", () => {
19
26
  beforeEach(() => {
@@ -14,19 +14,24 @@ export const useAutoSeek = (resetHUDTimer = noop) => {
14
14
  const [isRunning, setIsRunning] = useState<boolean>(false);
15
15
  const [direction, setDirection] = useState<Nullable<SEEK_TYPE>>(null);
16
16
  const [multiplier, setMultiplier] = useState(1);
17
+
17
18
  const intervalTimer = React.useRef(null);
18
19
  const prevRunning = usePrevious(isRunning);
19
20
 
20
21
  const setIntervalTimer = (cb) => {
21
- intervalTimer.current = setInterval(cb, ON_HOLD_INTERVAL);
22
+ if (intervalTimer.current) {
23
+ return;
24
+ }
25
+
26
+ intervalTimer.current = setInterval(() => {
27
+ intervalTimer.current = null;
28
+ cb();
29
+ }, ON_HOLD_INTERVAL);
22
30
  };
23
31
 
24
32
  const clearIntervalTimer = () => {
25
- try {
26
- clearInterval(intervalTimer.current);
27
- } catch (err) {
28
- intervalTimer.current = null;
29
- }
33
+ clearInterval(intervalTimer.current);
34
+ intervalTimer.current = null;
30
35
  };
31
36
 
32
37
  const resetState = () => {
@@ -37,6 +42,21 @@ export const useAutoSeek = (resetHUDTimer = noop) => {
37
42
  };
38
43
 
39
44
  useEffect(() => {
45
+ const player = playerManager.getInstanceController();
46
+ if (!player) return;
47
+
48
+ if (isRunning) {
49
+ player?.enterDelayedSeek();
50
+ } else if (!isRunning) {
51
+ player.exitDelayedSeek();
52
+ }
53
+ }, [isRunning]);
54
+
55
+ useEffect(() => {
56
+ if (!isRunning) {
57
+ return;
58
+ }
59
+
40
60
  const player = playerManager.getInstanceController();
41
61
 
42
62
  if (!player) return;
@@ -45,7 +65,7 @@ export const useAutoSeek = (resetHUDTimer = noop) => {
45
65
  player[direction](R.min(MAX_SEEK_TIME, SKIP_TIME_BASE * multiplier));
46
66
  resetHUDTimer();
47
67
  }
48
- }, [multiplier, direction]);
68
+ }, [multiplier, direction, isRunning]);
49
69
 
50
70
  useEffect(() => {
51
71
  if (isRunning && isRunning !== prevRunning) {
@@ -75,7 +95,7 @@ export const useAutoSeek = (resetHUDTimer = noop) => {
75
95
  setDirection(eventType);
76
96
  setIsRunning(true);
77
97
  },
78
- [isRunning]
98
+ [isRunning, autoSeekStop]
79
99
  );
80
100
 
81
101
  return React.useMemo(
@@ -9,11 +9,11 @@ export const useSeek = (resetHUDTimer = noop) => {
9
9
  const { autoSeekStart, autoSeekStop } = useAutoSeek(resetHUDTimer);
10
10
 
11
11
  const onForwardPress = () => {
12
- handlePressEvent(SEEK_TYPE.FORWARD);
12
+ // handlePressEvent(SEEK_TYPE.FORWARD);
13
13
  };
14
14
 
15
15
  const onRewindPress = () => {
16
- handlePressEvent(SEEK_TYPE.REWIND);
16
+ // handlePressEvent(SEEK_TYPE.REWIND);
17
17
  };
18
18
 
19
19
  const onForwardPressIn = () => {
@@ -21,6 +21,7 @@ export const useSeek = (resetHUDTimer = noop) => {
21
21
  };
22
22
 
23
23
  const onForwardPressOut = () => {
24
+ handlePressEvent(SEEK_TYPE.FORWARD);
24
25
  autoSeekStop();
25
26
  };
26
27
 
@@ -37,6 +38,7 @@ export const useSeek = (resetHUDTimer = noop) => {
37
38
  };
38
39
 
39
40
  const onRewindPressOut = () => {
41
+ handlePressEvent(SEEK_TYPE.REWIND);
40
42
  autoSeekStop();
41
43
  };
42
44
 
@@ -1,4 +1,4 @@
1
- import { useCallback, useRef } from "react";
1
+ import { useCallback, useRef, useState } from "react";
2
2
  import * as R from "ramda";
3
3
  import {
4
4
  MAX_SEEK_TIME,
@@ -10,13 +10,11 @@ import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils";
10
10
 
11
11
  type SeekType = SEEK_TYPE | null;
12
12
 
13
- const getSkipTime = (multiplier) => {
14
- const skipTime = R.min(MAX_SEEK_TIME, SKIP_TIME_BASE * multiplier);
15
-
16
- return skipTime;
17
- };
13
+ const getSkipTime = (multiplier) =>
14
+ R.min(MAX_SEEK_TIME, SKIP_TIME_BASE * multiplier);
18
15
 
19
16
  export const useTapSeek = () => {
17
+ const [resumeTimer, setResumeTimer] = useState<NodeJS.Timeout>(null);
20
18
  const onPressMultiplier = useRef(1);
21
19
  const lastPressTime = useRef(0);
22
20
  const currentOnPressEventType = useRef<SeekType>(null);
@@ -46,12 +44,41 @@ export const useTapSeek = () => {
46
44
  }
47
45
 
48
46
  const seekTime = getSkipTime(onPressMultiplier.current);
47
+
49
48
  player[eventType](seekTime);
50
49
 
50
+ if (resumeTimer) {
51
+ clearTimeout(resumeTimer);
52
+
53
+ const timer = setTimeout(() => {
54
+ player.exitDelayedSeek();
55
+ setResumeTimer(null);
56
+ }, 2000);
57
+
58
+ setResumeTimer(timer);
59
+ }
60
+
61
+ const clicksBeforePause = 3;
62
+
63
+ // Only pause player if user starts to seek on large increments
64
+ if (onPressMultiplier.current > clicksBeforePause) {
65
+ if (!player.isPaused()) {
66
+ player.enterDelayedSeek();
67
+
68
+ const timer = setTimeout(() => {
69
+ player.exitDelayedSeek();
70
+ setResumeTimer(null);
71
+ }, 2000);
72
+
73
+ clearTimeout(resumeTimer);
74
+ setResumeTimer(timer);
75
+ }
76
+ }
77
+
51
78
  lastPressTime.current = now;
52
79
  currentOnPressEventType.current = eventType;
53
80
  },
54
- [resetMultiplier]
81
+ [resetMultiplier, resumeTimer]
55
82
  );
56
83
 
57
84
  return handlePressEvent;
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
  import { View } from "react-native";
3
- import { renderHook, cleanup } from "@testing-library/react-hooks";
3
+ import { cleanup, renderHook } from "@testing-library/react-hooks";
4
4
  import configureStore from "redux-mock-store";
5
5
  import { Provider } from "react-redux";
6
6
 
@@ -12,6 +12,8 @@ jest.mock("@applicaster/zapp-react-native-utils/localizationUtils", () => ({
12
12
  useIsRTL: jest.fn(() => mock_rtl_flag),
13
13
  }));
14
14
 
15
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/navigation");
16
+
15
17
  const { useCellResolver } = require("../useCellResolver");
16
18
 
17
19
  describe("cellResolver", () => {
@@ -1,7 +1,7 @@
1
1
  // @ts-ignore
2
2
  import { View } from "react-native";
3
3
 
4
- const mockData: NavigationScreenData = {
4
+ const mockData = {
5
5
  screen: {
6
6
  id: "screen-id",
7
7
  name: "screen",
@@ -40,3 +40,5 @@ const mockData: NavigationScreenData = {
40
40
  };
41
41
 
42
42
  export const useScreenContext = jest.fn().mockReturnValue(mockData);
43
+
44
+ export const useNavbarState = () => ({ visible: true });
@@ -2,6 +2,8 @@ export { useCurrentScreenData } from "./useCurrentScreenData";
2
2
 
3
3
  export { useTargetScreenData } from "./useTargetScreenData";
4
4
 
5
+ export { useScreenData } from "./useScreenData";
6
+
5
7
  export {
6
8
  useScreenContext,
7
9
  useLegacyScreenData,
@@ -2,11 +2,12 @@ import { useContext, useMemo } from "react";
2
2
 
3
3
  import { useModalNavigationContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ModalNavigationContext";
4
4
  import { useNestedNavigationContext } from "@applicaster/zapp-react-native-ui-components/Contexts/NestedNavigationContext";
5
- import { useNavigation } from "../navigation";
5
+ import { useNavigation } from "../navigation/useNavigation";
6
6
 
7
7
  import { ScreenContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenContext";
8
+ import { ScreenDataContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenDataContext";
8
9
 
9
- function useScreenContextV2() {
10
+ export function useScreenContextV2() {
10
11
  return useContext(ScreenContext);
11
12
  }
12
13
 
@@ -44,9 +45,9 @@ export function useSetNavbarState() {
44
45
 
45
46
  export function useScreenContext(): NavigationScreenData {
46
47
  const navigator = useNavigation();
48
+ // Cover cases where screenContext is used before navigation is initialized
49
+ const screenData = useContext(ScreenDataContext) || navigator.data;
47
50
 
48
- const screenKey = useMemo(() => navigator?.key, [navigator]);
49
- const screenData = useMemo(() => navigator?.data, [screenKey]);
50
51
  const modalScreenData = navigator?.modalData;
51
52
 
52
53
  const isModal = useModalNavigationContext();
@@ -4,7 +4,7 @@ import { useRivers } from "../state";
4
4
 
5
5
  const logger = utilsLogger.addSubsystem("useScreenData");
6
6
 
7
- export const useScreenData = (screenId?: string): ZappRiver => {
7
+ export const useScreenData = (screenId: string): ZappRiver => {
8
8
  const rivers = useRivers();
9
9
 
10
10
  React.useEffect(() => {
@@ -1,6 +1,7 @@
1
1
  import React from "react";
2
2
  import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
3
3
  import * as R from "ramda";
4
+ import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
4
5
 
5
6
  export function getTargetScreenData(
6
7
  entry: ZappEntry,
@@ -35,6 +36,12 @@ export function getTargetScreenData(
35
36
  return rivers?.[screenId];
36
37
  }
37
38
 
39
+ export function getTargetScreenDataFromEntry(entry: ZappEntry): ZappRiver {
40
+ const { rivers, contentTypes } = appStore.get(["rivers", "contentTypes"]);
41
+
42
+ return getTargetScreenData(entry, rivers, contentTypes);
43
+ }
44
+
38
45
  export const useTargetScreenData = (entry: ZappEntry) => {
39
46
  const { rivers, contentTypes } = usePickFromState(["rivers", "contentTypes"]);
40
47
 
@@ -1,7 +1,9 @@
1
1
  import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
2
2
 
3
+ const riversSelector = ["rivers"];
4
+
3
5
  export const useRivers = () => {
4
- const { rivers } = usePickFromState(["rivers"]);
6
+ const { rivers } = usePickFromState(riversSelector as any);
5
7
 
6
8
  return rivers;
7
9
  };
@@ -13,10 +13,7 @@ export const useListenEventBusEvent = (
13
13
  // TODO: remove after implementing ReactNativeEventBusBridge on iOS
14
14
  if (Platform.OS === "android") {
15
15
  // subscribe to DeviceEventEmitter using subsciption ID
16
- const sub = DeviceEventEmitter.addListener(
17
- subscriptionID,
18
- handler
19
- );
16
+ const sub = DeviceEventEmitter.addListener(subscriptionID, handler);
20
17
 
21
18
  // ask native to start routing these events to RN
22
19
  ReactNativeEventBusBridge.subscribe(subscriptionID, { [source]: events });