@applicaster/zapp-react-native-utils 13.0.0-alpha.3637360334 → 13.0.0-alpha.3732093334

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 (51) hide show
  1. package/actionsExecutor/ActionExecutorContext.tsx +34 -1
  2. package/actionsExecutor/consts.ts +4 -0
  3. package/appUtils/HooksManager/Hook.ts +4 -0
  4. package/appUtils/HooksManager/index.ts +2 -0
  5. package/appUtils/__tests__/__snapshots__/localizationsHelper.test.ts.snap +151 -0
  6. package/appUtils/__tests__/allZappLocales.ts +79 -0
  7. package/appUtils/__tests__/{localizationsHelper.test.js → localizationsHelper.test.ts} +11 -0
  8. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +2 -0
  9. package/appUtils/focusManager/index.ios.ts +14 -4
  10. package/appUtils/focusManager/index.ts +13 -0
  11. package/appUtils/focusManager/utils/__tests__/findChild.test.ts +35 -0
  12. package/appUtils/focusManager/utils/index.ts +5 -0
  13. package/appUtils/localizationsHelper.ts +31 -0
  14. package/appUtils/playerManager/playerHooks/usePlayerCurrentTime.tsx +11 -7
  15. package/appUtils/playerManager/usePlayerTitleSummaryOverlay.tsx +7 -1
  16. package/cellUtils/index.ts +9 -5
  17. package/componentsUtils/__tests__/isFirstComponentGallery.test.ts +33 -0
  18. package/componentsUtils/__tests__/isFirstComponentScreenPicker.test.ts +33 -0
  19. package/componentsUtils/__tests__/isGallery.test.ts +33 -0
  20. package/componentsUtils/__tests__/isScreenPicker.test.ts +33 -0
  21. package/componentsUtils/index.ts +39 -0
  22. package/dateUtils/index.ts +19 -0
  23. package/focusManager/FocusManager.ts +3 -1
  24. package/focusManager/index.ts +4 -30
  25. package/functionUtils/index.ts +25 -0
  26. package/index.d.ts +0 -12
  27. package/localizationUtils/index.ts +8 -13
  28. package/manifestUtils/defaultManifestConfigurations/generalContent.js +13 -0
  29. package/manifestUtils/defaultManifestConfigurations/player.js +6 -8
  30. package/manifestUtils/keys.js +27 -2
  31. package/modalState/__tests__/index.test.ts +51 -0
  32. package/modalState/index.ts +32 -2
  33. package/navigationUtils/__tests__/__snapshots__/navigationUtils.test.js.snap +1 -0
  34. package/navigationUtils/index.ts +3 -1
  35. package/numberUtils/__tests__/toPositiveNumberWithDefault.test.ts +23 -3
  36. package/numberUtils/index.ts +4 -0
  37. package/package.json +2 -2
  38. package/playerUtils/configurationGenerator.ts +6 -16
  39. package/reactHooks/advertising/index.ts +5 -5
  40. package/reactHooks/feed/useBatchLoading.ts +10 -12
  41. package/reactHooks/feed/useFeedLoader.tsx +13 -6
  42. package/reactHooks/navigation/useRoute.ts +2 -2
  43. package/reactHooks/screen/__tests__/useScreenBackgroundColor.test.tsx +69 -0
  44. package/reactHooks/screen/useScreenBackgroundColor.ts +3 -15
  45. package/reactHooks/useListenEventBusEvent.ts +1 -1
  46. package/utils/README.md +30 -0
  47. package/utils/chunk.ts +5 -0
  48. package/utils/index.ts +5 -0
  49. package/utils/times.ts +5 -0
  50. package/zappFrameworkUtils/localStorageHelper.ts +6 -0
  51. package/zappFrameworkUtils/types.ts +11 -0
@@ -26,6 +26,8 @@ import {
26
26
  useContentTypes,
27
27
  usePickFromState,
28
28
  } from "@applicaster/zapp-react-native-redux/hooks";
29
+ import { TOGGLE_FLAG_MAX_ITEMS_REACHED_EVENT } from "./consts";
30
+ import { postEvent } from "../reactHooks/useSubscriberFor";
29
31
 
30
32
  export const { log_error, log_info, log_debug } = createLogger({
31
33
  subsystem: "ActionExecutorContext",
@@ -74,6 +76,19 @@ function findParentComponent(
74
76
  return null;
75
77
  }
76
78
 
79
+ // send all data just in case (like for message string formatting)
80
+ // Type is not exported for now
81
+ type MaxTagsReachedEvent = {
82
+ selectedItems: string[];
83
+ maxItems: number;
84
+ tag: string;
85
+ keyNamespace: string;
86
+ };
87
+
88
+ async function onMaxTagsReached(data: MaxTagsReachedEvent) {
89
+ postEvent(TOGGLE_FLAG_MAX_ITEMS_REACHED_EVENT, [data]);
90
+ }
91
+
77
92
  const prepareDefaultActions = (actionExecutor) => {
78
93
  actionExecutor.registerAction("localStorageSet", async (action) => {
79
94
  const namespaces = action.options.content;
@@ -234,6 +249,23 @@ const prepareDefaultActions = (actionExecutor) => {
234
249
  if (selectedItems.includes(tag)) {
235
250
  await multiSelectProvider.removeItem(tag);
236
251
  } else {
252
+ const maxItems = action.options?.max_items;
253
+
254
+ if (maxItems && selectedItems.length >= maxItems) {
255
+ log_info(
256
+ `handleAction: localStorageToggleFlag event reached max items limit: ${maxItems}, cannot add tag: ${tag}`
257
+ );
258
+
259
+ await onMaxTagsReached({
260
+ selectedItems,
261
+ maxItems,
262
+ tag,
263
+ keyNamespace,
264
+ });
265
+
266
+ return ActionResult.Cancel;
267
+ }
268
+
237
269
  await multiSelectProvider.addItem(tag);
238
270
  }
239
271
  } else {
@@ -274,7 +306,7 @@ export function withActionExecutor(Component) {
274
306
  useEffect(() => {
275
307
  return _actionExecutor.registerAction(
276
308
  "navigateToScreen",
277
- async (action: ActionType, _context?: Record<string, any>) => {
309
+ async (action: ActionType, context?: Record<string, any>) => {
278
310
  const screenType = action.options?.typeMapping;
279
311
 
280
312
  if (!screenType) {
@@ -301,6 +333,7 @@ export function withActionExecutor(Component) {
301
333
  return ActionResult.Error;
302
334
  }
303
335
 
336
+ context?.callback?.({ success: false, error: null, abort: true });
304
337
  navigator.replace(river);
305
338
 
306
339
  return ActionResult.Success;
@@ -0,0 +1,4 @@
1
+ export const TOGGLE_FLAG_MAX_ITEMS_REACHED_EVENT =
2
+ "action.localStorageToggleFlag.maxItemsReached";
3
+
4
+ export const ACTION_EXECUTOR_EVENT_SOURCE = "ActionExecutor";
@@ -65,6 +65,10 @@ 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
+
68
72
  this.state = hookState(event);
69
73
  this.manager.subscriber.invokeHandler(event, ...args);
70
74
  }
@@ -269,6 +269,7 @@ export function HooksManager({
269
269
  payload = initialPayload,
270
270
  callback: callbackArg,
271
271
  cancelled,
272
+ abort,
272
273
  }) {
273
274
  let callback = callbackArg;
274
275
 
@@ -308,6 +309,7 @@ export function HooksManager({
308
309
  hookPlugin.setStateAndNotify(HOOKS_EVENTS.CANCEL, {
309
310
  hookPlugin,
310
311
  payload,
312
+ abort,
311
313
  });
312
314
 
313
315
  const isHookInHomescreen = payload?.home;
@@ -0,0 +1,151 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`toDayJSLocaleMap [af] returns a valid dayjs locale 1`] = `"af"`;
4
+
5
+ exports[`toDayJSLocaleMap [ar] returns a valid dayjs locale 1`] = `"ar"`;
6
+
7
+ exports[`toDayJSLocaleMap [bg] returns a valid dayjs locale 1`] = `"bg"`;
8
+
9
+ exports[`toDayJSLocaleMap [bn] returns a valid dayjs locale 1`] = `"bn"`;
10
+
11
+ exports[`toDayJSLocaleMap [bo] returns a valid dayjs locale 1`] = `"bo"`;
12
+
13
+ exports[`toDayJSLocaleMap [ca] returns a valid dayjs locale 1`] = `"ca"`;
14
+
15
+ exports[`toDayJSLocaleMap [cs] returns a valid dayjs locale 1`] = `"cs"`;
16
+
17
+ exports[`toDayJSLocaleMap [da] returns a valid dayjs locale 1`] = `"da"`;
18
+
19
+ exports[`toDayJSLocaleMap [de] returns a valid dayjs locale 1`] = `"de"`;
20
+
21
+ exports[`toDayJSLocaleMap [el] returns a valid dayjs locale 1`] = `"el"`;
22
+
23
+ exports[`toDayJSLocaleMap [en] returns a valid dayjs locale 1`] = `"en"`;
24
+
25
+ exports[`toDayJSLocaleMap [en-GB] returns a valid dayjs locale 1`] = `"en-gb"`;
26
+
27
+ exports[`toDayJSLocaleMap [en-UK] returns a valid dayjs locale 1`] = `"en-gb"`;
28
+
29
+ exports[`toDayJSLocaleMap [es] returns a valid dayjs locale 1`] = `"es"`;
30
+
31
+ exports[`toDayJSLocaleMap [es-LA] returns a valid dayjs locale 1`] = `"es"`;
32
+
33
+ exports[`toDayJSLocaleMap [es-MX] returns a valid dayjs locale 1`] = `"es-mx"`;
34
+
35
+ exports[`toDayJSLocaleMap [es-US] returns a valid dayjs locale 1`] = `"es-us"`;
36
+
37
+ exports[`toDayJSLocaleMap [et] returns a valid dayjs locale 1`] = `"et"`;
38
+
39
+ exports[`toDayJSLocaleMap [eu] returns a valid dayjs locale 1`] = `"eu"`;
40
+
41
+ exports[`toDayJSLocaleMap [fa] returns a valid dayjs locale 1`] = `"fa"`;
42
+
43
+ exports[`toDayJSLocaleMap [fi] returns a valid dayjs locale 1`] = `"fi"`;
44
+
45
+ exports[`toDayJSLocaleMap [fj] returns a valid dayjs locale 1`] = `"en"`;
46
+
47
+ exports[`toDayJSLocaleMap [fr] returns a valid dayjs locale 1`] = `"fr"`;
48
+
49
+ exports[`toDayJSLocaleMap [ga] returns a valid dayjs locale 1`] = `"ga"`;
50
+
51
+ exports[`toDayJSLocaleMap [gu] returns a valid dayjs locale 1`] = `"gu"`;
52
+
53
+ exports[`toDayJSLocaleMap [he] returns a valid dayjs locale 1`] = `"he"`;
54
+
55
+ exports[`toDayJSLocaleMap [hi] returns a valid dayjs locale 1`] = `"hi"`;
56
+
57
+ exports[`toDayJSLocaleMap [hr] returns a valid dayjs locale 1`] = `"hr"`;
58
+
59
+ exports[`toDayJSLocaleMap [hu] returns a valid dayjs locale 1`] = `"hu"`;
60
+
61
+ exports[`toDayJSLocaleMap [hy] returns a valid dayjs locale 1`] = `"hy-am"`;
62
+
63
+ exports[`toDayJSLocaleMap [id] returns a valid dayjs locale 1`] = `"id"`;
64
+
65
+ exports[`toDayJSLocaleMap [is] returns a valid dayjs locale 1`] = `"is"`;
66
+
67
+ exports[`toDayJSLocaleMap [it] returns a valid dayjs locale 1`] = `"it"`;
68
+
69
+ exports[`toDayJSLocaleMap [ja] returns a valid dayjs locale 1`] = `"ja"`;
70
+
71
+ exports[`toDayJSLocaleMap [ka] returns a valid dayjs locale 1`] = `"ka"`;
72
+
73
+ exports[`toDayJSLocaleMap [km] returns a valid dayjs locale 1`] = `"km"`;
74
+
75
+ exports[`toDayJSLocaleMap [ko] returns a valid dayjs locale 1`] = `"ko"`;
76
+
77
+ exports[`toDayJSLocaleMap [la] returns a valid dayjs locale 1`] = `"en"`;
78
+
79
+ exports[`toDayJSLocaleMap [lt] returns a valid dayjs locale 1`] = `"lt"`;
80
+
81
+ exports[`toDayJSLocaleMap [lv] returns a valid dayjs locale 1`] = `"lv"`;
82
+
83
+ exports[`toDayJSLocaleMap [mi] returns a valid dayjs locale 1`] = `"mi"`;
84
+
85
+ exports[`toDayJSLocaleMap [mk] returns a valid dayjs locale 1`] = `"mk"`;
86
+
87
+ exports[`toDayJSLocaleMap [ml] returns a valid dayjs locale 1`] = `"ml"`;
88
+
89
+ exports[`toDayJSLocaleMap [mn] returns a valid dayjs locale 1`] = `"mn"`;
90
+
91
+ exports[`toDayJSLocaleMap [mr] returns a valid dayjs locale 1`] = `"mr"`;
92
+
93
+ exports[`toDayJSLocaleMap [ms] returns a valid dayjs locale 1`] = `"ms"`;
94
+
95
+ exports[`toDayJSLocaleMap [mt] returns a valid dayjs locale 1`] = `"mt"`;
96
+
97
+ exports[`toDayJSLocaleMap [ne] returns a valid dayjs locale 1`] = `"ne"`;
98
+
99
+ exports[`toDayJSLocaleMap [nl] returns a valid dayjs locale 1`] = `"nl"`;
100
+
101
+ exports[`toDayJSLocaleMap [no] returns a valid dayjs locale 1`] = `"en"`;
102
+
103
+ exports[`toDayJSLocaleMap [pa] returns a valid dayjs locale 1`] = `"pa-in"`;
104
+
105
+ exports[`toDayJSLocaleMap [pl] returns a valid dayjs locale 1`] = `"pl"`;
106
+
107
+ exports[`toDayJSLocaleMap [pt] returns a valid dayjs locale 1`] = `"pt"`;
108
+
109
+ exports[`toDayJSLocaleMap [pt-BR] returns a valid dayjs locale 1`] = `"pt-br"`;
110
+
111
+ exports[`toDayJSLocaleMap [qu] returns a valid dayjs locale 1`] = `"en"`;
112
+
113
+ exports[`toDayJSLocaleMap [ro] returns a valid dayjs locale 1`] = `"ro"`;
114
+
115
+ exports[`toDayJSLocaleMap [ru] returns a valid dayjs locale 1`] = `"ru"`;
116
+
117
+ exports[`toDayJSLocaleMap [sk] returns a valid dayjs locale 1`] = `"sk"`;
118
+
119
+ exports[`toDayJSLocaleMap [sl] returns a valid dayjs locale 1`] = `"sl"`;
120
+
121
+ exports[`toDayJSLocaleMap [sm] returns a valid dayjs locale 1`] = `"en"`;
122
+
123
+ exports[`toDayJSLocaleMap [sq] returns a valid dayjs locale 1`] = `"sq"`;
124
+
125
+ exports[`toDayJSLocaleMap [sr] returns a valid dayjs locale 1`] = `"sr"`;
126
+
127
+ exports[`toDayJSLocaleMap [sv] returns a valid dayjs locale 1`] = `"sv"`;
128
+
129
+ exports[`toDayJSLocaleMap [sw] returns a valid dayjs locale 1`] = `"sw"`;
130
+
131
+ exports[`toDayJSLocaleMap [ta] returns a valid dayjs locale 1`] = `"ta"`;
132
+
133
+ exports[`toDayJSLocaleMap [te] returns a valid dayjs locale 1`] = `"te"`;
134
+
135
+ exports[`toDayJSLocaleMap [th] returns a valid dayjs locale 1`] = `"th"`;
136
+
137
+ exports[`toDayJSLocaleMap [to] returns a valid dayjs locale 1`] = `"en"`;
138
+
139
+ exports[`toDayJSLocaleMap [tr] returns a valid dayjs locale 1`] = `"tr"`;
140
+
141
+ exports[`toDayJSLocaleMap [tt] returns a valid dayjs locale 1`] = `"en"`;
142
+
143
+ exports[`toDayJSLocaleMap [uk] returns a valid dayjs locale 1`] = `"uk"`;
144
+
145
+ exports[`toDayJSLocaleMap [ur] returns a valid dayjs locale 1`] = `"ur"`;
146
+
147
+ exports[`toDayJSLocaleMap [uz] returns a valid dayjs locale 1`] = `"uz"`;
148
+
149
+ exports[`toDayJSLocaleMap [vi] returns a valid dayjs locale 1`] = `"vi"`;
150
+
151
+ exports[`toDayJSLocaleMap [zh] returns a valid dayjs locale 1`] = `"zh"`;
@@ -0,0 +1,79 @@
1
+ export default [
2
+ // common locales from https://rubygems.org/gems/language_list
3
+ "en",
4
+ "fr",
5
+ "de",
6
+ "nl",
7
+ "ru",
8
+ "es",
9
+ "he",
10
+ "hy",
11
+ "sq",
12
+ "af",
13
+ "ar",
14
+ "eu",
15
+ "bn",
16
+ "bg",
17
+ "zh",
18
+ "cs",
19
+ "km",
20
+ "ca",
21
+ "hr",
22
+ "da",
23
+ "et",
24
+ "fj",
25
+ "fi",
26
+ "gu",
27
+ "ka",
28
+ "hu",
29
+ "hi",
30
+ "it",
31
+ "is",
32
+ "id",
33
+ "ga",
34
+ "ja",
35
+ "ko",
36
+ "la",
37
+ "lv",
38
+ "lt",
39
+ "ms",
40
+ "mi",
41
+ "mn",
42
+ "mt",
43
+ "mk",
44
+ "el",
45
+ "mr",
46
+ "ml",
47
+ "ne",
48
+ "no",
49
+ "pa",
50
+ "pl",
51
+ "pt",
52
+ "fa",
53
+ "qu",
54
+ "ro",
55
+ "sk",
56
+ "sm",
57
+ "sr",
58
+ "sw",
59
+ "sv",
60
+ "sl",
61
+ "to",
62
+ "tr",
63
+ "th",
64
+ "te",
65
+ "bo",
66
+ "tt",
67
+ "ta",
68
+ "uk",
69
+ "ur",
70
+ "uz",
71
+ "vi",
72
+ // Extra locales https://github.com/applicaster/zapp/blob/5604794f15cfd270ae494b0db85e542de4cebfaa/config/additional_languages.yml
73
+ "en-UK",
74
+ "en-GB",
75
+ "es-LA",
76
+ "pt-BR",
77
+ "es-MX",
78
+ "es-US",
79
+ ];
@@ -8,6 +8,7 @@ const {
8
8
  getLocale,
9
9
  getLanguageCode,
10
10
  getCountryCode,
11
+ toDayJSLocaleMap,
11
12
  } = require("../localizationsHelper");
12
13
 
13
14
  describe("getLocale", () => {
@@ -69,3 +70,13 @@ describe("getCountryCode", () => {
69
70
  expect(currentValue).toEqual(expectedValue);
70
71
  });
71
72
  });
73
+
74
+ describe("toDayJSLocaleMap", () => {
75
+ const allZappLocales: string[] = require("./allZappLocales").default;
76
+
77
+ it.each(allZappLocales)("[%s] returns a valid dayjs locale", (locale) => {
78
+ const dayJSLocale = toDayJSLocaleMap(locale);
79
+ expect(dayJSLocale).toBeDefined();
80
+ expect(dayJSLocale).toMatchSnapshot();
81
+ });
82
+ });
@@ -5,6 +5,7 @@ exports[`focusManager should be defined 1`] = `
5
5
  "blur": [Function],
6
6
  "disableFocus": [Function],
7
7
  "enableFocus": [Function],
8
+ "findPreferredFocusChild": [Function],
8
9
  "focusableTree": Tree {
9
10
  "loadingCounter": 0,
10
11
  "root": {
@@ -60,6 +61,7 @@ exports[`focusManagerIOS should be defined 1`] = `
60
61
  "getCurrentGroup": [Function],
61
62
  "getGroupById": [Function],
62
63
  "getGroupRootById": [Function],
64
+ "getPreferredFocusChild": [Function],
63
65
  "invokeHandler": [Function],
64
66
  "isGroupItemFocused": [Function],
65
67
  "moveFocus": [Function],
@@ -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
  })();
@@ -534,6 +534,18 @@ export const focusManager = (function () {
534
534
 
535
535
  const { on, invokeHandler, removeHandler } = eventHandler;
536
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
+
537
549
  /**
538
550
  * this is the list of the functions available externally
539
551
  * when importing the focus manager
@@ -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
+ );
@@ -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,
@@ -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
  };
@@ -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
+ });