@applicaster/zapp-react-native-utils 13.0.0-rc.98 → 14.0.0-rc.1

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 (39) hide show
  1. package/actionsExecutor/ActionExecutorContext.tsx +55 -6
  2. package/actionsExecutor/consts.ts +4 -0
  3. package/appUtils/__tests__/__snapshots__/localizationsHelper.test.ts.snap +151 -0
  4. package/appUtils/__tests__/allZappLocales.ts +79 -0
  5. package/appUtils/__tests__/{localizationsHelper.test.js → localizationsHelper.test.ts} +11 -0
  6. package/appUtils/accessibilityManager/const.ts +18 -0
  7. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +1 -0
  8. package/appUtils/focusManager/index.ios.ts +14 -4
  9. package/appUtils/focusManager/utils/__tests__/findChild.test.ts +35 -0
  10. package/appUtils/focusManager/utils/index.ts +5 -0
  11. package/appUtils/localizationsHelper.ts +10 -2
  12. package/appUtils/playerManager/playerHooks/usePlayerCurrentTime.tsx +11 -7
  13. package/cellUtils/index.ts +9 -5
  14. package/componentsUtils/index.ts +8 -1
  15. package/localizationUtils/index.ts +3 -3
  16. package/manifestUtils/defaultManifestConfigurations/generalContent.js +13 -0
  17. package/manifestUtils/defaultManifestConfigurations/player.js +0 -8
  18. package/manifestUtils/index.js +2 -0
  19. package/manifestUtils/keys.js +27 -2
  20. package/package.json +2 -2
  21. package/playerUtils/configurationGenerator.ts +0 -16
  22. package/playerUtils/index.ts +17 -0
  23. package/reactHooks/app/useAppState.ts +2 -2
  24. package/reactHooks/feed/useBatchLoading.ts +10 -12
  25. package/reactHooks/navigation/{useGetTabBarHeight.ts → getTabBarHeight.ts} +1 -1
  26. package/reactHooks/navigation/useGetBottomTabBarHeight.ts +10 -3
  27. package/reactHooks/navigation/useNavigationPluginData.ts +8 -4
  28. package/reactHooks/navigation/useNavigationType.ts +4 -2
  29. package/reactHooks/screen/__tests__/useScreenBackgroundColor.test.tsx +69 -0
  30. package/reactHooks/screen/useScreenBackgroundColor.ts +3 -15
  31. package/reactHooks/state/README.md +79 -0
  32. package/reactHooks/state/ZStoreProvider.tsx +71 -0
  33. package/reactHooks/state/__tests__/ZStoreProvider.test.tsx +66 -0
  34. package/reactHooks/state/index.ts +2 -0
  35. package/reactHooks/useListenEventBusEvent.ts +1 -1
  36. package/reactUtils/index.ts +9 -0
  37. package/typeGuards/index.ts +3 -0
  38. package/utils/index.ts +1 -1
  39. package/zappFrameworkUtils/localStorageHelper.ts +32 -10
@@ -5,9 +5,14 @@ import {
5
5
  actionExecutor as _actionExecutor,
6
6
  ActionResult,
7
7
  } from "./ActionExecutor";
8
- import { batchSaveToLocalStorage } from "../zappFrameworkUtils/localStorageHelper";
9
- import { QUICK_BRICK_EVENTS } from "@applicaster/zapp-react-native-bridge/QuickBrick";
8
+ import {
9
+ batchRemoveAllFromNamespaceForStorage,
10
+ batchSave,
11
+ } from "../zappFrameworkUtils/localStorageHelper";
12
+ import { sessionStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/SessionStorage";
13
+
10
14
  import * as QuickBrickManager from "@applicaster/zapp-react-native-bridge/QuickBrick";
15
+ import { QUICK_BRICK_EVENTS } from "@applicaster/zapp-react-native-bridge/QuickBrick";
11
16
  import { showConfirmationDialog } from "../alertUtils";
12
17
  import { createCloudEvent, sendCloudEvent } from "../cloudEventsUtils";
13
18
  import { createLogger } from "../logger";
@@ -26,6 +31,9 @@ import {
26
31
  useContentTypes,
27
32
  usePickFromState,
28
33
  } from "@applicaster/zapp-react-native-redux/hooks";
34
+ import { TOGGLE_FLAG_MAX_ITEMS_REACHED_EVENT } from "./consts";
35
+ import { postEvent, useSubscriberFor } from "../reactHooks/useSubscriberFor";
36
+ import { APP_EVENTS } from "../appUtils/events";
29
37
 
30
38
  export const { log_error, log_info, log_debug } = createLogger({
31
39
  subsystem: "ActionExecutorContext",
@@ -74,10 +82,31 @@ function findParentComponent(
74
82
  return null;
75
83
  }
76
84
 
85
+ // send all data just in case (like for message string formatting)
86
+ // Type is not exported for now
87
+ type MaxTagsReachedEvent = {
88
+ selectedItems: string[];
89
+ maxItems: number;
90
+ tag: string;
91
+ keyNamespace: string;
92
+ };
93
+
94
+ async function onMaxTagsReached(data: MaxTagsReachedEvent) {
95
+ postEvent(TOGGLE_FLAG_MAX_ITEMS_REACHED_EVENT, [data]);
96
+ }
97
+
77
98
  const prepareDefaultActions = (actionExecutor) => {
78
99
  actionExecutor.registerAction("localStorageSet", async (action) => {
79
100
  const namespaces = action.options.content;
80
- await batchSaveToLocalStorage(namespaces);
101
+ await batchSave(namespaces, localStorage);
102
+ // TODO: Add support for ownershipKey and ownershipNamespace
103
+
104
+ return ActionResult.Success;
105
+ });
106
+
107
+ actionExecutor.registerAction("sessionStorageSet", async (action) => {
108
+ const namespaces = action.options.content;
109
+ await batchSave(namespaces, sessionStorage);
81
110
  // TODO: Add support for ownershipKey and ownershipNamespace
82
111
 
83
112
  return ActionResult.Success;
@@ -134,15 +163,13 @@ const prepareDefaultActions = (actionExecutor) => {
134
163
  actionExecutor.registerAction("confirmDialog", async (action) => {
135
164
  log_info("handleAction: confirmDialog event");
136
165
 
137
- const confirmationPromise = new Promise<ActionResult>((resolve) => {
166
+ return new Promise<ActionResult>((resolve) => {
138
167
  showConfirmationDialog({
139
168
  ...action.options,
140
169
  confirmCompletion: () => resolve(ActionResult.Success),
141
170
  cancelCompletion: () => resolve(ActionResult.Cancel),
142
171
  });
143
172
  });
144
-
145
- return confirmationPromise;
146
173
  });
147
174
 
148
175
  actionExecutor.registerAction("sendCloudEvent", async (action, context) => {
@@ -241,6 +268,13 @@ const prepareDefaultActions = (actionExecutor) => {
241
268
  `handleAction: localStorageToggleFlag event reached max items limit: ${maxItems}, cannot add tag: ${tag}`
242
269
  );
243
270
 
271
+ await onMaxTagsReached({
272
+ selectedItems,
273
+ maxItems,
274
+ tag,
275
+ keyNamespace,
276
+ });
277
+
244
278
  return ActionResult.Cancel;
245
279
  }
246
280
 
@@ -281,9 +315,24 @@ export function withActionExecutor(Component) {
281
315
  };
282
316
  }, []);
283
317
 
318
+ useSubscriberFor(APP_EVENTS.onLogout, () => {
319
+ log_debug(
320
+ "User profile: onLogout event received, clearing user profile data"
321
+ );
322
+
323
+ const userAccountKey = "user_account";
324
+ void batchRemoveAllFromNamespaceForStorage(userAccountKey, localStorage);
325
+
326
+ void batchRemoveAllFromNamespaceForStorage(
327
+ userAccountKey,
328
+ sessionStorage
329
+ );
330
+ });
331
+
284
332
  useEffect(() => {
285
333
  return _actionExecutor.registerAction(
286
334
  "navigateToScreen",
335
+
287
336
  async (action: ActionType, context?: Record<string, any>) => {
288
337
  const screenType = action.options?.typeMapping;
289
338
 
@@ -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";
@@ -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
+ });
@@ -47,4 +47,22 @@ export const BUTTON_ACCESSIBILITY_KEYS = {
47
47
  label: "accessibility_fullscreen_label",
48
48
  hint: "accessibility_fullscreen_hint",
49
49
  },
50
+ // EPG-specific buttons
51
+ now: {
52
+ label: "accessibility_now_label",
53
+ hint: "accessibility_now_hint",
54
+ },
55
+ day: {
56
+ label: "accessibility_day_label",
57
+ hint: "accessibility_day_hint",
58
+ },
59
+ program: {
60
+ label: "accessibility_program_label",
61
+ hint: "accessibility_program_hint",
62
+ },
63
+ // Menu-specific buttons
64
+ menu_item: {
65
+ label: "accessibility_menu_item_label",
66
+ hint: "accessibility_menu_item_hint",
67
+ },
50
68
  } as const;
@@ -61,6 +61,7 @@ exports[`focusManagerIOS should be defined 1`] = `
61
61
  "getCurrentGroup": [Function],
62
62
  "getGroupById": [Function],
63
63
  "getGroupRootById": [Function],
64
+ "getPreferredFocusChild": [Function],
64
65
  "invokeHandler": [Function],
65
66
  "isGroupItemFocused": [Function],
66
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
  })();
@@ -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,12 @@ 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
+ * */
68
74
  export const toDayJSLocaleMap = (code) => {
69
75
  const map = {
70
76
  hy: "hy-am",
@@ -73,10 +79,12 @@ export const toDayJSLocaleMap = (code) => {
73
79
  no: null,
74
80
  pa: "pa-in",
75
81
  qu: null,
76
- "es-LA": "es",
77
82
  sm: null,
78
83
  to: null,
79
84
  xh: null,
85
+ tt: null,
86
+ "es-LA": "es",
87
+ "en-UK": "en-gb",
80
88
  };
81
89
 
82
90
  if (map[code] === null) {
@@ -84,7 +92,7 @@ export const toDayJSLocaleMap = (code) => {
84
92
  } else if (map[code]) {
85
93
  return map[code];
86
94
  } else {
87
- return code;
95
+ return code.toLowerCase();
88
96
  }
89
97
  };
90
98
 
@@ -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
  };
@@ -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
 
@@ -1,13 +1,19 @@
1
1
  const GROUP = "group-qb";
2
2
  const GROUP_INFO = "group-info-qb";
3
+ const GROUP_INFO_OLD = "group-info";
3
4
  const EMPTY_GROUP_COMPONENT = "empty_group_component";
4
5
 
5
6
  const GALLERY = "gallery-qb";
6
7
 
7
8
  const SCREEN_PICKER = "screen-picker-qb-tv";
8
9
 
10
+ const HORIZONTAL_LIST = "horizontal_list_qb";
11
+
9
12
  export const isGallery = (item): boolean => item?.component_type === GALLERY;
10
13
 
14
+ export const isHorizontalList = (item): boolean =>
15
+ item?.component_type === HORIZONTAL_LIST;
16
+
11
17
  export const isScreenPicker = (item): boolean =>
12
18
  item?.component_type === SCREEN_PICKER;
13
19
 
@@ -29,4 +35,5 @@ export const isEmptyGroup = (item): boolean =>
29
35
  item?.component_type === EMPTY_GROUP_COMPONENT;
30
36
 
31
37
  export const isGroupInfo = (item): boolean =>
32
- item?.component_type === GROUP_INFO;
38
+ item?.component_type === GROUP_INFO ||
39
+ item?.component_type === GROUP_INFO_OLD;
@@ -1,8 +1,8 @@
1
1
  import { ViewStyle } from "react-native";
2
2
 
3
3
  import { useMemo } from "react";
4
- import { getUILanguage } from "../appUtils/localizationsHelper";
5
- import { utilsLogger } from "../logger";
4
+ import { getUILanguage } from "@applicaster/zapp-react-native-utils/appUtils/localizationsHelper";
5
+ import { utilsLogger } from "@applicaster/zapp-react-native-utils/logger";
6
6
 
7
7
  export {
8
8
  getLocale,
@@ -10,7 +10,7 @@ export {
10
10
  getCountryCode,
11
11
  getLocalizations,
12
12
  getUILanguage,
13
- } from "../appUtils/localizationsHelper";
13
+ } from "@applicaster/zapp-react-native-utils/appUtils/localizationsHelper";
14
14
 
15
15
  const RTL_LOCALES = [
16
16
  "ar", // Arabic
@@ -251,6 +251,19 @@ const generalContent = () => ({
251
251
  key: "screen_title",
252
252
  initial_value: null,
253
253
  },
254
+ {
255
+ type: "text_input",
256
+ label: "Maximum Selection Reached - Alert Description",
257
+ key: "msg_maximum_selection_reached_message",
258
+ initial_value:
259
+ "You’ve reached the maximum number of items you can select.",
260
+ },
261
+ {
262
+ type: "text_input",
263
+ label: "Maximum Selection Reached - Alert Button",
264
+ key: "msg_maximum_selection_reached_message_ok_button",
265
+ initial_value: "Got it",
266
+ },
254
267
  ],
255
268
  },
256
269
  advertising: {