@applicaster/zapp-react-native-utils 13.0.0-alpha.3013227535 → 13.0.0-alpha.3044917147

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 (114) hide show
  1. package/README.md +0 -10
  2. package/actionsExecutor/ActionExecutorContext.tsx +159 -12
  3. package/actionsExecutor/consts.ts +4 -0
  4. package/analyticsUtils/currentScreenProperities.ts +6 -4
  5. package/appUtils/HooksManager/Hook.ts +4 -0
  6. package/appUtils/HooksManager/index.ts +3 -1
  7. package/appUtils/__tests__/__snapshots__/localizationsHelper.test.ts.snap +151 -0
  8. package/appUtils/__tests__/allZappLocales.ts +79 -0
  9. package/appUtils/__tests__/{localizationsHelper.test.js → localizationsHelper.test.ts} +11 -0
  10. package/appUtils/accessibilityManager/const.ts +18 -0
  11. package/appUtils/contextKeysManager/contextResolver.ts +1 -12
  12. package/appUtils/contextKeysManager/index.ts +2 -2
  13. package/appUtils/contextKeysManager/utils/index.ts +2 -1
  14. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +3 -0
  15. package/appUtils/focusManager/index.ios.ts +14 -4
  16. package/appUtils/focusManager/index.ts +22 -9
  17. package/appUtils/focusManager/utils/__tests__/findChild.test.ts +35 -0
  18. package/appUtils/focusManager/utils/index.ts +5 -0
  19. package/appUtils/keyCodes/index.ts +11 -7
  20. package/appUtils/localizationsHelper.ts +31 -0
  21. package/appUtils/orientationHelper.ts +11 -13
  22. package/appUtils/platform/platformUtils.ts +30 -3
  23. package/appUtils/playerManager/index.ts +1 -1
  24. package/appUtils/playerManager/player.ts +24 -12
  25. package/appUtils/playerManager/playerHooks/usePlayerCurrentTime.tsx +11 -7
  26. package/appUtils/playerManager/playerNative.ts +18 -3
  27. package/appUtils/playerManager/usePlayerTitleSummaryOverlay.tsx +7 -1
  28. package/cellUtils/index.ts +9 -5
  29. package/componentsUtils/__tests__/isFirstComponentGallery.test.ts +33 -0
  30. package/componentsUtils/__tests__/isFirstComponentScreenPicker.test.ts +33 -0
  31. package/componentsUtils/__tests__/isGallery.test.ts +33 -0
  32. package/componentsUtils/__tests__/isScreenPicker.test.ts +33 -0
  33. package/componentsUtils/index.ts +39 -0
  34. package/dateUtils/index.ts +19 -0
  35. package/equalUtils/__tests__/isEqual/combination.test.ts +263 -0
  36. package/equalUtils/index.ts +49 -30
  37. package/focusManager/FocusManager.ts +14 -2
  38. package/focusManager/index.ts +23 -4
  39. package/functionUtils/index.ts +46 -8
  40. package/index.d.ts +0 -12
  41. package/localizationUtils/index.ts +8 -13
  42. package/manifestUtils/__tests__/createConfig.test.js +0 -2
  43. package/manifestUtils/defaultManifestConfigurations/generalContent.js +13 -0
  44. package/manifestUtils/defaultManifestConfigurations/player.js +9 -3
  45. package/manifestUtils/fonts/index.js +5 -6
  46. package/manifestUtils/index.js +2 -0
  47. package/manifestUtils/keys.js +27 -2
  48. package/manifestUtils/sharedConfiguration/screenPicker/__tests__/utils.test.js +304 -0
  49. package/manifestUtils/sharedConfiguration/screenPicker/stylesFields.js +875 -0
  50. package/manifestUtils/sharedConfiguration/screenPicker/utils.js +130 -0
  51. package/modalState/__tests__/index.test.ts +51 -0
  52. package/modalState/index.ts +32 -2
  53. package/navigationUtils/__tests__/__snapshots__/navigationUtils.test.js.snap +1 -0
  54. package/navigationUtils/index.ts +3 -1
  55. package/numberUtils/__tests__/toPositiveNumberWithDefault.test.ts +23 -3
  56. package/numberUtils/index.ts +11 -5
  57. package/package.json +4 -10
  58. package/playerUtils/configurationGenerator.ts +9 -11
  59. package/playerUtils/index.ts +23 -0
  60. package/reactHooks/advertising/index.ts +5 -5
  61. package/reactHooks/app/useAppState.ts +2 -2
  62. package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +5 -7
  63. package/reactHooks/autoscrolling/useTrackedView.ts +12 -1
  64. package/reactHooks/feed/useBatchLoading.ts +10 -12
  65. package/reactHooks/feed/useFeedLoader.tsx +17 -8
  66. package/reactHooks/hookModal/hooks/useHookModalScreenData.ts +39 -0
  67. package/reactHooks/layout/useDimensions/useDimensions.ts +8 -0
  68. package/reactHooks/navigation/__mocks__/useNavigation.ts +3 -0
  69. package/reactHooks/navigation/__tests__/index.test.tsx +6 -17
  70. package/reactHooks/navigation/{useGetTabBarHeight.ts → getTabBarHeight.ts} +2 -4
  71. package/reactHooks/navigation/index.ts +13 -23
  72. package/reactHooks/navigation/useGetBottomTabBarHeight.ts +10 -3
  73. package/reactHooks/navigation/useNavigationPluginData.ts +8 -4
  74. package/reactHooks/navigation/useNavigationType.ts +4 -2
  75. package/reactHooks/navigation/useProfilerLogging.ts +15 -0
  76. package/reactHooks/navigation/useRoute.ts +4 -6
  77. package/reactHooks/screen/__tests__/useScreenBackgroundColor.test.tsx +69 -0
  78. package/reactHooks/screen/useScreenBackgroundColor.ts +3 -15
  79. package/reactHooks/state/README.md +79 -0
  80. package/reactHooks/state/ZStoreProvider.tsx +71 -0
  81. package/reactHooks/state/__tests__/ZStoreProvider.test.tsx +66 -0
  82. package/reactHooks/state/index.ts +2 -0
  83. package/reactHooks/state/useRefWithInitialValue.ts +14 -0
  84. package/reactHooks/state/useStateFromSubscribe.ts +23 -0
  85. package/reactHooks/useListenEventBusEvent.ts +1 -1
  86. package/reactUtils/__tests__/__snapshots__/reactUtilsHelpers.test.js.snap +16 -4
  87. package/reactUtils/__tests__/reactUtils.test.js +1 -19
  88. package/reactUtils/__tests__/reactUtilsHelpers.test.js +72 -71
  89. package/reactUtils/createContext/__tests__/__snapshots__/helpers.test.js.snap +6 -72
  90. package/reactUtils/createContext/__tests__/__snapshots__/provider.test.js.snap +3 -28
  91. package/reactUtils/createContext/__tests__/__snapshots__/withConsumer.test.js.snap +3 -4
  92. package/reactUtils/createContext/__tests__/__snapshots__/withProvider.test.js.snap +7 -3
  93. package/reactUtils/createContext/__tests__/helpers.test.js +107 -121
  94. package/reactUtils/createContext/__tests__/{index.test.js → index.test.ts} +2 -2
  95. package/reactUtils/createContext/__tests__/provider.test.js +4 -6
  96. package/reactUtils/createContext/__tests__/withConsumer.test.js +12 -12
  97. package/reactUtils/createContext/__tests__/withProvider.test.js +4 -4
  98. package/reactUtils/createContext/helpers.ts +6 -1
  99. package/reactUtils/createContext/index.ts +11 -25
  100. package/reactUtils/index.ts +9 -11
  101. package/services/js2native.ts +673 -0
  102. package/testUtils/index.tsx +0 -10
  103. package/time/BackgroundTimer.ts +55 -0
  104. package/time/Timer.ts +6 -6
  105. package/typeGuards/index.ts +3 -0
  106. package/utils/README.md +30 -0
  107. package/utils/chunk.ts +5 -0
  108. package/utils/index.ts +5 -0
  109. package/utils/times.ts +5 -0
  110. package/zappFrameworkUtils/localStorageHelper.ts +38 -10
  111. package/zappFrameworkUtils/loginPluginUtils.ts +6 -2
  112. package/zappFrameworkUtils/types.ts +11 -0
  113. package/reactUtils/createContext/__tests__/index-no-react.test.js +0 -27
  114. /package/reactUtils/createContext/__tests__/__snapshots__/{index.test.js.snap → index.test.ts.snap} +0 -0
@@ -1,8 +1,10 @@
1
1
  import { NativeModules } from "react-native";
2
2
  import * as R from "ramda";
3
+
3
4
  import { Tree } from "./treeDataStructure/Tree";
4
5
  import { findFocusableNode } from "./treeDataStructure/Utils";
5
6
  import { subscriber } from "../../functionUtils";
7
+ import { findChild } from "./utils";
6
8
 
7
9
  const { FocusableManagerModule } = NativeModules;
8
10
 
@@ -52,10 +54,7 @@ export const focusManager = (function () {
52
54
  const node = focusableTree.findInTree(groupId);
53
55
 
54
56
  if (node?.children?.length > 0) {
55
- const preferredFocus = R.compose(
56
- R.find(R.pathEq(["component", "props", "preferredFocus"], true)),
57
- R.prop("children")
58
- )(node);
57
+ const preferredFocus = findChild(true, node);
59
58
 
60
59
  if (preferredFocus?.component?.isGroup) {
61
60
  return getPreferredFocusInGroup({ groupId: preferredFocus?.id });
@@ -382,6 +381,16 @@ export const focusManager = (function () {
382
381
  forceFocusOnItem({ focusableItem, callback });
383
382
  }
384
383
 
384
+ function getPreferredFocusChild(id) {
385
+ const node = focusableTree.findInTree(id);
386
+
387
+ if (node?.children?.length > 0) {
388
+ return findChild(true, node) || findChild(false, node);
389
+ }
390
+
391
+ return node;
392
+ }
393
+
385
394
  return {
386
395
  on,
387
396
  invokeHandler,
@@ -402,5 +411,6 @@ export const focusManager = (function () {
402
411
  getCurrentGroup,
403
412
  getGroupRootById,
404
413
  isGroupItemFocused,
414
+ getPreferredFocusChild,
405
415
  };
406
416
  })();
@@ -334,15 +334,14 @@ export const focusManager = (function () {
334
334
  return { success: false };
335
335
  }
336
336
 
337
- function treeLoaded(val, node) {
338
- logger.log({
339
- message: "treeLoaded",
340
- data: {
341
- val,
342
- node,
343
- currentFocusNode,
344
- },
345
- });
337
+ function treeLoaded(_val, _node) {}
338
+
339
+ function pressIn() {
340
+ const currentFocusable = getCurrentFocus();
341
+
342
+ if (currentFocusable) {
343
+ currentFocusable.pressIn(null);
344
+ }
346
345
  }
347
346
 
348
347
  /**
@@ -535,6 +534,18 @@ export const focusManager = (function () {
535
534
 
536
535
  const { on, invokeHandler, removeHandler } = eventHandler;
537
536
 
537
+ function findPreferredFocusChild(id) {
538
+ const node = getCurrentFocusNode(id);
539
+
540
+ const preferredFocus = findPriorityItem([node]);
541
+
542
+ if (isNilOrEmpty(preferredFocus)) {
543
+ return undefined;
544
+ }
545
+
546
+ return preferredFocus[0];
547
+ }
548
+
538
549
  /**
539
550
  * this is the list of the functions available externally
540
551
  * when importing the focus manager
@@ -552,6 +563,7 @@ export const focusManager = (function () {
552
563
  invokeHandler,
553
564
  removeHandler,
554
565
  longPress,
566
+ pressIn,
555
567
  pressOut,
556
568
  enableFocus,
557
569
  setInitialFocus,
@@ -563,5 +575,6 @@ export const focusManager = (function () {
563
575
  isGroupItemFocused,
564
576
  recoverFocus,
565
577
  isCurrentFocusOnTheTopScreen,
578
+ findPreferredFocusChild,
566
579
  };
567
580
  })();
@@ -0,0 +1,35 @@
1
+ import { findChild } from "../index";
2
+
3
+ describe("findChild", () => {
4
+ const makeNode = (children = []) => ({ children });
5
+
6
+ const child = (preferredFocus: boolean) => ({
7
+ component: { props: { preferredFocus } },
8
+ });
9
+
10
+ it("returns the child with preferredFocus=true", () => {
11
+ const node = makeNode([child(false), child(true), child(false)]);
12
+ expect(findChild(true, node)).toBe(node.children[1]);
13
+ });
14
+
15
+ it("returns the child with preferredFocus=false", () => {
16
+ const node = makeNode([child(false), child(true), child(false)]);
17
+ expect(findChild(false, node)).toBe(node.children[0]);
18
+ });
19
+
20
+ it("returns undefined if no child matches preferredFocus", () => {
21
+ const node = makeNode([child(false), child(false)]);
22
+ expect(findChild(true, node)).toBeUndefined();
23
+ });
24
+
25
+ it("returns undefined if node has no children", () => {
26
+ const node = makeNode();
27
+ expect(findChild(true, node)).toBeUndefined();
28
+ expect(findChild(false, node)).toBeUndefined();
29
+ });
30
+
31
+ it("returns undefined if children is not an array", () => {
32
+ const node = { children: null };
33
+ expect(findChild(true, node)).toBeUndefined();
34
+ });
35
+ });
@@ -98,3 +98,8 @@ export const waitForContent = (focusableTree) => {
98
98
  conditionFn: contentHasAnyChildren,
99
99
  });
100
100
  };
101
+
102
+ export const findChild = (preferredFocus: boolean, node) =>
103
+ (node?.children || []).find(
104
+ (child) => child?.component?.props?.preferredFocus === preferredFocus
105
+ );
@@ -1,5 +1,3 @@
1
- import * as R from "ramda";
2
-
3
1
  // more keys can be added here. Simply make sure
4
2
  // the key matches the code value
5
3
 
@@ -34,12 +32,18 @@ export function keyCode(key) {
34
32
  keyCode(keyEvent).matchesAny("Enter", "Backspace", KEYS.ArrowLeft, ...)
35
33
  */
36
34
 
37
- const matches = R.compose(
38
- R.either(R.eqProps("code", key), R.eqProps("keyCode", key)),
39
- R.when(R.is(String), R.prop(R.__, KEYS))
40
- );
35
+ const matches = (compareKey) => {
36
+ // If compareKey is a string, look it up in KEYS object
37
+ const keyToCompare =
38
+ typeof compareKey === "string" ? KEYS[compareKey] : compareKey;
39
+
40
+ // Compare either code or keyCode properties
41
+ return (
42
+ keyToCompare?.code === key.code || keyToCompare?.keyCode === key.keyCode
43
+ );
44
+ };
41
45
 
42
- const matchesAny = (...args) => R.any(matches, args);
46
+ const matchesAny = (...args) => args.some(matches);
43
47
 
44
48
  const direction = () =>
45
49
  matchesAny(...ARROW_KEYS) ? directionHelper(key) : null;
@@ -65,6 +65,37 @@ export function getUILanguage() {
65
65
  return getLanguageCode();
66
66
  }
67
67
 
68
+ /**
69
+ * fallback to en for missing dayjslocalization
70
+ * converts the locale code to a dayjs compatible locale code
71
+ * lowercase input code is expected, e.g. "en-GB" => "en-gb"
72
+ * @param {string} code - locale code, e.g. "en", "fr", "es-LA"
73
+ * */
74
+ export const toDayJSLocaleMap = (code) => {
75
+ const map = {
76
+ hy: "hy-am",
77
+ fj: null,
78
+ la: null,
79
+ no: null,
80
+ pa: "pa-in",
81
+ qu: null,
82
+ sm: null,
83
+ to: null,
84
+ xh: null,
85
+ tt: null,
86
+ "es-LA": "es",
87
+ "en-UK": "en-gb",
88
+ };
89
+
90
+ if (map[code] === null) {
91
+ return "en";
92
+ } else if (map[code]) {
93
+ return map[code];
94
+ } else {
95
+ return code.toLowerCase();
96
+ }
97
+ };
98
+
68
99
  /**
69
100
  * returns the localization for the app based on the locale. If the locale is null
70
101
  * or undefined, or if the given locale doesn't exist in the localizations,
@@ -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
- import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks/device/useIsTablet";
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
@@ -77,13 +77,22 @@ export class ClosedCaptioningManager {
77
77
 
78
78
  private constructor() {
79
79
  this.initialize();
80
+
81
+ window["vizioDebug"] = {
82
+ setCCEnabled: (isCCEnabled: boolean) => {
83
+ this.ccState$.next(isCCEnabled);
84
+ },
85
+ };
80
86
  }
81
87
 
82
88
  static getInstance(): ClosedCaptioningManager {
83
89
  if (!ClosedCaptioningManager.ccManagerInstance) {
90
+ log_debug("ClosedCaptioningManager: Creating new instance");
84
91
  ClosedCaptioningManager.ccManagerInstance = new ClosedCaptioningManager();
85
92
  }
86
93
 
94
+ log_debug("ClosedCaptioningManager: Returning existing instance");
95
+
87
96
  return ClosedCaptioningManager.ccManagerInstance;
88
97
  }
89
98
 
@@ -92,23 +101,41 @@ export class ClosedCaptioningManager {
92
101
  PLATFORM_KEYS.deviceClosedCaptioningEnabled
93
102
  );
94
103
 
95
- log_debug("Initializing closed captioning manager", { existingState });
96
-
97
104
  if (existingState !== null) {
105
+ log_debug(
106
+ `ClosedCaptioningManager: Setting initial state to ${existingState}`
107
+ );
108
+
98
109
  this.ccState$.next(existingState === "true" || existingState === true);
110
+ } else {
111
+ log_debug("ClosedCaptioningManager: No existing state found");
99
112
  }
100
113
 
101
114
  if (hasVizioAPIs()) {
115
+ log_debug("ClosedCaptioningManager: Setting closed captioning handler");
116
+
102
117
  window.VIZIO!.setClosedCaptionHandler((isCCEnabled) => {
103
- log_debug("CC state changed", { isCCEnabled });
118
+ log_debug(
119
+ "ClosedCaptioningManager: There was a change in the CC state",
120
+ { isCCEnabled }
121
+ );
104
122
 
105
123
  sessionStorage.setItem(
106
124
  PLATFORM_KEYS.deviceClosedCaptioningEnabled,
107
125
  isCCEnabled
108
126
  );
109
127
 
128
+ log_debug(
129
+ "ClosedCaptioningManager: Saved CC state to session storage",
130
+ { isCCEnabled }
131
+ );
132
+
110
133
  this.ccState$.next(isCCEnabled);
111
134
  });
135
+ } else {
136
+ log_debug(
137
+ "ClosedCaptioningManager: Vizio APIs are not available, skipping setup"
138
+ );
112
139
  }
113
140
  }
114
141
 
@@ -452,7 +452,7 @@ export class PlayerManager {
452
452
  if (this.castingReceiver) {
453
453
  this.castingReceiver.closeNativePlayer();
454
454
  } else {
455
- this.getActivePlayer().closeNativePlayer();
455
+ this.getActivePlayer()?.closeNativePlayer();
456
456
  this.invokeHandler("close");
457
457
  }
458
458
  }
@@ -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
- if (!isEmptyOrNil(seekableDuration) && seekableDuration >= 0) {
205
- this.playerState.seekableDuration = seekableDuration;
206
- }
204
+ this.updateContentDuration({ seekableDuration, duration, currentTime });
205
+ }
206
+ };
207
207
 
208
- if (!isEmptyOrNil(duration) && duration >= 0) {
209
- this.playerState.contentDuration = duration;
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
- if (!isEmptyOrNil(currentTime) && currentTime >= 0) {
213
- this.playerState.contentPosition = currentTime;
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 = (event) => {
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
@@ -12,7 +12,7 @@ import { useThrottle } from "../../../functionUtils";
12
12
  export const usePlayerCurrentTime = (
13
13
  listenerId: string,
14
14
  playerId?: string
15
- ): number | null => {
15
+ ): { currentTime: number | null; cancel: () => void } => {
16
16
  const [currentTime, setCurrentTime] = useState<number | null>(null);
17
17
  const player: Player = usePlayer(playerId);
18
18
 
@@ -20,15 +20,17 @@ export const usePlayerCurrentTime = (
20
20
  if (player) {
21
21
  const newCurrentTime = player.getContentPosition();
22
22
 
23
- setCurrentTime((prevTime) =>
24
- prevTime !== newCurrentTime ? newCurrentTime : prevTime
25
- );
23
+ setCurrentTime(newCurrentTime);
26
24
  }
27
25
  }, [player]);
28
26
 
27
+ // We update the current time every ~250ms since player can have rate set to 2x.
28
+ // We update more often than 2x a second to avoid noticiable 'pulsation' in update,
29
+ // since native level also updates the current time every ~250ms,
30
+ // and combined with assorted delays it creates interference pattern.
29
31
  const { throttledFunction: throttledUpdate, cancel } = useThrottle(
30
32
  updateCurrentTime,
31
- 1000
33
+ 1000 / 4
32
34
  );
33
35
 
34
36
  useEffect(() => {
@@ -36,7 +38,9 @@ export const usePlayerCurrentTime = (
36
38
  throttledUpdate();
37
39
 
38
40
  const listeners = {
39
- onVideoProgress: throttledUpdate,
41
+ onVideoProgress: () => {
42
+ setCurrentTime(player.getContentPosition());
43
+ },
40
44
  };
41
45
 
42
46
  const id = `${listenerId}_player_current_time`;
@@ -53,5 +57,5 @@ export const usePlayerCurrentTime = (
53
57
  }
54
58
  }, [player, listenerId, throttledUpdate, cancel]);
55
59
 
56
- return currentTime;
60
+ return { currentTime, cancel };
57
61
  };
@@ -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
- if (this.startPosition) {
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 => {
@@ -3,12 +3,18 @@ import { OverlaysObserver } from "./OverlayObserver/OverlaysObserver";
3
3
  import { useFeedLoader } from "../../reactHooks";
4
4
  import { usePlayer } from "./usePlayer";
5
5
 
6
+ const getLiveUrl = (url: string) => {
7
+ // Use the replace method with a regular expression to replace all instances of subClass with livePage
8
+ return url.replace(new RegExp("subClass", "g"), "livePage");
9
+ };
10
+
6
11
  export const useTitleSummaryOverlay = (
7
- feedUrl,
8
12
  entry,
9
13
  isLive = false,
10
14
  interval: number
11
15
  ) => {
16
+ // TODO: Have generic place for live url from configuration. Currently its hardcoded for client.
17
+ const feedUrl = getLiveUrl(entry?.extensions?.live?.channel_src ?? "");
12
18
  const player = usePlayer();
13
19
  const [title, setTitle] = useState<string | number>(entry?.title || "");
14
20
  const [summary, setSummary] = useState<string | number>(entry?.summary || "");
@@ -2,12 +2,16 @@ import * as R from "ramda";
2
2
  import dayjs from "dayjs";
3
3
  import validateColor from "validate-color";
4
4
 
5
+ import { transformColorCode as fixColorHexCode } from "@applicaster/zapp-react-native-utils/transform";
6
+ import {
7
+ capitalize,
8
+ isString,
9
+ } from "@applicaster/zapp-react-native-utils/stringUtils";
10
+ import { cellUtilsLogger } from "@applicaster/zapp-react-native-utils/cellUtils/logger";
11
+ import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
12
+ import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
13
+
5
14
  import { toNumberWithDefault, toNumberWithDefaultZero } from "../numberUtils";
6
- import { transformColorCode as fixColorHexCode } from "../transform";
7
- import { capitalize, isString } from "../stringUtils";
8
- import { cellUtilsLogger } from "./logger";
9
- import { isWeb } from "../reactUtils";
10
- import { isNotNil } from "../reactUtils/helpers";
11
15
 
12
16
  const CUSTOM_KEY = "other";
13
17
 
@@ -0,0 +1,33 @@
1
+ import { isFirstComponentGallery } from "../index";
2
+
3
+ describe("isFirstComponentGallery", () => {
4
+ it("should return true if the first component is a gallery", () => {
5
+ const components = [
6
+ { component_type: "gallery-qb" },
7
+ { component_type: "other-component" },
8
+ ];
9
+
10
+ expect(isFirstComponentGallery(components)).toBe(true);
11
+ });
12
+
13
+ it("should return false if the first component is not a gallery", () => {
14
+ const components = [
15
+ { component_type: "other-component" },
16
+ { component_type: "gallery-qb" },
17
+ ];
18
+
19
+ expect(isFirstComponentGallery(components)).toBe(false);
20
+ });
21
+
22
+ it("should return false if the components array is empty", () => {
23
+ const components = [];
24
+
25
+ expect(isFirstComponentGallery(components)).toBe(false);
26
+ });
27
+
28
+ it("should return false if the components array is undefined", () => {
29
+ const components = undefined;
30
+
31
+ expect(isFirstComponentGallery(components)).toBe(false);
32
+ });
33
+ });
@@ -0,0 +1,33 @@
1
+ import { isFirstComponentScreenPicker } from "..";
2
+
3
+ describe("isFirstComponentScreenPicker", () => {
4
+ it("should return true if the first component screen-picker", () => {
5
+ const items = [{ component_type: "screen-picker-qb-tv" }];
6
+ expect(isFirstComponentScreenPicker(items)).toBe(true);
7
+ });
8
+
9
+ it("should return false if the first component type is not screen-picker", () => {
10
+ const items = [{ component_type: "other-component" }];
11
+ expect(isFirstComponentScreenPicker(items)).toBe(false);
12
+ });
13
+
14
+ it("should return false if the component type is undefined", () => {
15
+ const items = [{ component_type: undefined }];
16
+ expect(isFirstComponentScreenPicker(items)).toBe(false);
17
+ });
18
+
19
+ it("should return false if the item is null", () => {
20
+ const items = null;
21
+ expect(isFirstComponentScreenPicker(items)).toBe(false);
22
+ });
23
+
24
+ it("should return false if the item is undefined", () => {
25
+ const items = undefined;
26
+ expect(isFirstComponentScreenPicker(items)).toBe(false);
27
+ });
28
+
29
+ it("should return false if the item is an empty object", () => {
30
+ const items = {};
31
+ expect(isFirstComponentScreenPicker(items)).toBe(false);
32
+ });
33
+ });
@@ -0,0 +1,33 @@
1
+ import { isGallery } from "..";
2
+
3
+ describe("isGallery", () => {
4
+ it("should return true if the component type is 'gallery-qb'", () => {
5
+ const item = { component_type: "gallery-qb" };
6
+ expect(isGallery(item)).toBe(true);
7
+ });
8
+
9
+ it("should return false if the component type is not 'gallery-qb'", () => {
10
+ const item = { component_type: "other-component" };
11
+ expect(isGallery(item)).toBe(false);
12
+ });
13
+
14
+ it("should return false if the component type is undefined", () => {
15
+ const item = { component_type: undefined };
16
+ expect(isGallery(item)).toBe(false);
17
+ });
18
+
19
+ it("should return false if the item is null", () => {
20
+ const item = null;
21
+ expect(isGallery(item)).toBe(false);
22
+ });
23
+
24
+ it("should return false if the item is undefined", () => {
25
+ const item = undefined;
26
+ expect(isGallery(item)).toBe(false);
27
+ });
28
+
29
+ it("should return false if the item is an empty object", () => {
30
+ const item = {};
31
+ expect(isGallery(item)).toBe(false);
32
+ });
33
+ });