@applicaster/zapp-react-native-utils 12.0.0-alpha.4433422595 → 12.0.0-alpha.4510795609

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 (71) hide show
  1. package/analyticsUtils/AnalyticsEvents/index.ts +13 -0
  2. package/analyticsUtils/AnalyticsEvents/searchEvents.ts +21 -0
  3. package/analyticsUtils/AnalyticsEvents/sendShareEvent.ts +21 -0
  4. package/analyticsUtils/analyticsPlugins.ts +25 -0
  5. package/analyticsUtils/events.ts +12 -0
  6. package/analyticsUtils/index.tsx +12 -0
  7. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +2 -3
  8. package/appUtils/focusManager/__tests__/focusManager.test.js +4 -24
  9. package/appUtils/focusManager/__tests__/index.test.ts +2 -3
  10. package/appUtils/focusManager/index.ts +76 -77
  11. package/appUtils/focusManager/treeDataStructure/Utils/index.js +30 -15
  12. package/appUtils/focusManager/utils/index.ts +100 -0
  13. package/appUtils/focusManagerAux/index.ts +76 -0
  14. package/appUtils/focusManagerAux/utils/index.ts +100 -0
  15. package/appUtils/keyCodes/keys.web.js +8 -0
  16. package/appUtils/keyCodes/keysByPlatform.js +11 -1
  17. package/appUtils/platform/const.ts +25 -0
  18. package/appUtils/platform/index.ts +3 -0
  19. package/appUtils/platform/platformUtils.ts +223 -0
  20. package/appUtils/playerManager/index.ts +11 -5
  21. package/appUtils/playerManager/nativePlayerManager.ts +12 -0
  22. package/appUtils/playerManager/player.ts +1 -3
  23. package/appUtils/playerManager/playerNative.ts +10 -14
  24. package/appUtils/playerManager/usePlayerControllerSetup.tsx +6 -1
  25. package/arrayUtils/__tests__/isFilledArray.test.ts +63 -0
  26. package/arrayUtils/index.ts +5 -0
  27. package/booleanUtils/index.ts +4 -4
  28. package/cellUtils/__tests__/cellUtils.test.ts +9 -20
  29. package/cellUtils/index.ts +6 -12
  30. package/dateUtils/index.ts +11 -0
  31. package/headersUtils/index.ts +1 -0
  32. package/inflatedUrlUtils/index.ts +4 -4
  33. package/keyboardState/index.ts +0 -0
  34. package/localizationUtils/__tests__/localizationUtils.test.ts +72 -53
  35. package/localizationUtils/index.ts +13 -22
  36. package/manifestUtils/defaultManifestConfigurations/generalContent.js +6 -0
  37. package/manifestUtils/defaultManifestConfigurations/player.js +2 -1
  38. package/numberUtils/__tests__/isLikeNumber.test.ts +18 -0
  39. package/numberUtils/index.ts +9 -18
  40. package/package.json +2 -2
  41. package/playerUtils/configurationGenerator.ts +3 -1
  42. package/reactHooks/autoscrolling/__tests__/{useTrackedView.test.ts → useTrackedView.test.tsx} +16 -1
  43. package/reactHooks/cell-click/__tests__/index.test.js +10 -1
  44. package/reactHooks/cell-click/index.ts +12 -12
  45. package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +30 -70
  46. package/reactHooks/feed/__tests__/useFeedRefresh.test.tsx +6 -2
  47. package/reactHooks/feed/__tests__/useInflatedUrl.test.ts +18 -3
  48. package/reactHooks/feed/useBuildPipesUrl.ts +7 -10
  49. package/reactHooks/feed/useFeedRefresh.tsx +9 -7
  50. package/reactHooks/feed/useInflatedUrl.ts +44 -15
  51. package/reactHooks/layout/__tests__/{index.test.js → index.test.tsx} +43 -29
  52. package/reactHooks/layout/getDeviceInfo/index.ts +4 -2
  53. package/reactHooks/layout/types/index.ts +1 -0
  54. package/reactHooks/layout/useDimensions/useDimensions.ts +5 -2
  55. package/reactHooks/navigation/__mocks__/index.ts +13 -0
  56. package/reactHooks/navigation/__mocks__/useNavigation.ts +3 -0
  57. package/reactHooks/navigation/__tests__/index.test.tsx +11 -8
  58. package/reactHooks/navigation/index.ts +43 -64
  59. package/reactHooks/screen/__tests__/useCurrentScreenData.test.tsx +21 -23
  60. package/reactHooks/screen/index.ts +2 -0
  61. package/reactHooks/screen/useScreenBackgroundColor.ts +23 -0
  62. package/reactHooks/screen/useScreenContext.ts +3 -3
  63. package/reactUtils/index.ts +102 -0
  64. package/screenState/index.ts +0 -8
  65. package/stringUtils/index.ts +5 -0
  66. package/testUtils/getByTestId.ts +14 -0
  67. package/testUtils/index.tsx +24 -22
  68. package/types.ts +4 -0
  69. package/appUtils/focusManager/FocusableEvents.ts +0 -8
  70. package/appUtils/focusManager/treeDataStructure/Utils/__tests__/Utils_android.test.js +0 -1094
  71. package/appUtils/focusManager/treeDataStructure/Utils/index.android.js +0 -261
@@ -7,6 +7,15 @@ import { sendHeaderClickEvent } from "./sendHeaderClickEvent";
7
7
  import { sendHardwareBackButtonClickEvent } from "./sendHardwareBackButtonClickEvent";
8
8
  import { sendDownloadsEvent } from "./sendDownloadsEvent";
9
9
  import { sendScreenEvent } from "./sendScreenEvent";
10
+ import {
11
+ sendSearchCanceledEvent,
12
+ sendSearchExecutedEvent,
13
+ } from "./searchEvents";
14
+
15
+ import {
16
+ sendShareInitiatedEvent,
17
+ sendShareSuccessEvent,
18
+ } from "./sendShareEvent";
10
19
 
11
20
  export {
12
21
  sendScreenEvent,
@@ -18,4 +27,8 @@ export {
18
27
  sendHeaderClickEvent,
19
28
  sendHardwareBackButtonClickEvent,
20
29
  sendDownloadsEvent,
30
+ sendSearchCanceledEvent,
31
+ sendSearchExecutedEvent,
32
+ sendShareInitiatedEvent,
33
+ sendShareSuccessEvent,
21
34
  };
@@ -0,0 +1,21 @@
1
+ import { log_debug } from "../logger";
2
+ import { postAnalyticEvent } from "../manager";
3
+ import { ANALYTICS_CORE_EVENTS } from "../events";
4
+
5
+ export const sendSearchCanceledEvent = (analyticsProps) => {
6
+ const eventName = ANALYTICS_CORE_EVENTS.SEARCH_CANCELED;
7
+ postAnalyticEvent(eventName, analyticsProps);
8
+
9
+ log_debug(`sendSearchCanceledEvent: send event: ${eventName}`, {
10
+ analyticsProps,
11
+ });
12
+ };
13
+
14
+ export const sendSearchExecutedEvent = (analyticsProps) => {
15
+ const eventName = ANALYTICS_CORE_EVENTS.SEARCH_EXECUTED;
16
+ postAnalyticEvent(eventName, analyticsProps);
17
+
18
+ log_debug(`sendSearchExecutedEvent: send event: ${eventName}`, {
19
+ analyticsProps,
20
+ });
21
+ };
@@ -0,0 +1,21 @@
1
+ import { log_debug } from "../logger";
2
+ import { postAnalyticEvent } from "../manager";
3
+ import { ANALYTICS_CORE_EVENTS } from "../events";
4
+
5
+ export const sendShareInitiatedEvent = (analyticsProps) => {
6
+ const eventName = ANALYTICS_CORE_EVENTS.SHARE_INITIATED;
7
+ postAnalyticEvent(eventName, analyticsProps);
8
+
9
+ log_debug(`sendShareInitiatedEvent: send event: ${eventName}`, {
10
+ analyticsProps,
11
+ });
12
+ };
13
+
14
+ export const sendShareSuccessEvent = (analyticsProps) => {
15
+ const eventName = ANALYTICS_CORE_EVENTS.SHARE_SUCCESS;
16
+ postAnalyticEvent(eventName, analyticsProps);
17
+
18
+ log_debug(`sendShareSuccessEvent: send event: ${eventName}`, {
19
+ analyticsProps,
20
+ });
21
+ };
@@ -0,0 +1,25 @@
1
+ import * as R from "ramda";
2
+ import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
3
+
4
+ export const getVideojsAnalyticPluginsConfig = async (entry: ZappEntry) => {
5
+ let videojsConfiguration = {};
6
+
7
+ const plugins = appStore.get("plugins");
8
+ const analyticPlugins = R.filter(R.propEq("type", "analytics"), plugins);
9
+
10
+ const configurations = await Promise.all(
11
+ analyticPlugins.map(async (plugin) => {
12
+ const { configuration, module } = plugin;
13
+
14
+ return module?.getPlayerConfiguration?.(configuration, entry);
15
+ })
16
+ );
17
+
18
+ configurations.forEach((analyticConfig) => {
19
+ if (typeof analyticConfig === "object" && analyticConfig !== null) {
20
+ videojsConfiguration = { ...videojsConfiguration, ...analyticConfig };
21
+ }
22
+ });
23
+
24
+ return videojsConfiguration;
25
+ };
@@ -23,10 +23,22 @@ export const TAPPING_EVENTS = {
23
23
  TAP_NAVBAR_BACK_BUTTON: "tap_navbar_back_button",
24
24
  };
25
25
 
26
+ export const SEARCH_EVENTS = {
27
+ SEARCH_EXECUTED: "search_executed",
28
+ SEARCH_CANCELED: "search_canceled",
29
+ };
30
+
31
+ export const SHARE_EVENTS = {
32
+ SHARE_INITIATED: "share_initiated",
33
+ SHARE_SUCCESS: "share_success",
34
+ };
35
+
26
36
  export const ANALYTICS_CORE_EVENTS = {
27
37
  ...LEGACY_EVENTS,
28
38
  ...SCREEN_VIEW_EVENTS,
29
39
  ...TAPPING_EVENTS,
40
+ ...SEARCH_EVENTS,
41
+ ...SHARE_EVENTS,
30
42
  };
31
43
 
32
44
  export const WEBVIEW_EVENTS = {
@@ -18,6 +18,10 @@ import {
18
18
  sendHardwareBackButtonClickEvent,
19
19
  sendDownloadsEvent,
20
20
  sendScreenEvent,
21
+ sendSearchCanceledEvent,
22
+ sendSearchExecutedEvent,
23
+ sendShareInitiatedEvent,
24
+ sendShareSuccessEvent,
21
25
  } from "./AnalyticsEvents";
22
26
 
23
27
  import { postAnalyticEvent } from "./manager";
@@ -66,6 +70,10 @@ type AnalyticsFunctions = {
66
70
  item: ZappEntry;
67
71
  state: OfflineItemState;
68
72
  }) => void;
73
+ sendSearchCanceledEvent: (props: AnalyticsDefaultProperties) => void;
74
+ sendSearchExecutedEvent: (props: AnalyticsDefaultProperties) => void;
75
+ sendShareInitiatedEvent: (props: AnalyticsDefaultProperties) => void;
76
+ sendShareSuccessEvent: (props: AnalyticsDefaultProperties) => void;
69
77
  };
70
78
 
71
79
  type GetAnalyticsFunctions = (
@@ -102,6 +110,10 @@ export function getAnalyticsFunctions({
102
110
  sendHardwareBackButtonClickEvent,
103
111
  sendScreenEvent,
104
112
  sendDownloadsEvent,
113
+ sendSearchCanceledEvent,
114
+ sendSearchExecutedEvent,
115
+ sendShareInitiatedEvent,
116
+ sendShareSuccessEvent,
105
117
  };
106
118
  }
107
119
 
@@ -3,9 +3,7 @@
3
3
  exports[`focusManager should be defined 1`] = `
4
4
  Object {
5
5
  "blur": [Function],
6
- "disableAutomaticFocus": [Function],
7
6
  "disableFocus": [Function],
8
- "enableAutomaticFocus": [Function],
9
7
  "enableFocus": [Function],
10
8
  "focusableTree": Tree {
11
9
  "loadingCounter": 0,
@@ -23,6 +21,7 @@ Object {
23
21
  "getGroupById": [Function],
24
22
  "getGroupRootById": [Function],
25
23
  "invokeHandler": [Function],
24
+ "isCurrentFocusOnTheTopScreen": [Function],
26
25
  "isFocusDisabled": [Function],
27
26
  "isGroupItemFocused": [Function],
28
27
  "longPress": [Function],
@@ -30,11 +29,11 @@ Object {
30
29
  "on": [Function],
31
30
  "press": [Function],
32
31
  "pressOut": [Function],
32
+ "recoverFocus": [Function],
33
33
  "register": [Function],
34
34
  "removeHandler": [Function],
35
35
  "setFocus": [Function],
36
36
  "setInitialFocus": [Function],
37
- "setupInitialFocus": [Function],
38
37
  "unregister": [Function],
39
38
  }
40
39
  `;
@@ -14,10 +14,8 @@ describe("focusManager", () => {
14
14
  expect(focusManager).toMatchSnapshot();
15
15
  });
16
16
 
17
- it("enableAutomaticFocus should not block setting focus", () => {
17
+ it("manages to land initial focus", () => {
18
18
  const mockSetFocus = jest.fn();
19
- focusManager.setupInitialFocus("1", true);
20
- focusManager.enableAutomaticFocus();
21
19
 
22
20
  focusManager.register({
23
21
  id: "1",
@@ -31,31 +29,13 @@ describe("focusManager", () => {
31
29
  },
32
30
  });
33
31
 
32
+ const { success } = focusManager.setInitialFocus();
33
+
34
+ expect(success).toBe(true);
34
35
  expect(mockSetFocus).toBeCalledTimes(1);
35
36
  expect(mockSetFocus).toBeCalledWith(null);
36
37
  });
37
38
 
38
- it("enableAutomaticFocus should block setting focus", () => {
39
- const mockSetFocus = jest.fn();
40
- focusManager.setupInitialFocus("1", true);
41
- focusManager.disableAutomaticFocus();
42
-
43
- focusManager.register({
44
- id: "1",
45
- component: {
46
- setFocus: mockSetFocus,
47
- willReceiveFocus: function () {},
48
- isMounted: function () {
49
- return true;
50
- },
51
- props: { groupId: "121" },
52
- },
53
- });
54
-
55
- expect(mockSetFocus).toBeCalledTimes(0);
56
- expect(mockSetFocus).not.toBeCalledWith(null);
57
- });
58
-
59
39
  describe("register", () => {});
60
40
  describe("unregister", () => {});
61
41
  describe("find", () => {});
@@ -18,7 +18,6 @@ describe("focusManager", () => {
18
18
  "on",
19
19
  "invokeHandler",
20
20
  "removeHandler",
21
- "setupInitialFocus",
22
21
  "longPress",
23
22
  "pressOut",
24
23
  "enableFocus",
@@ -27,10 +26,10 @@ describe("focusManager", () => {
27
26
  "isFocusDisabled",
28
27
  "getCurrentFocusNode",
29
28
  "focusableTree",
30
- "disableAutomaticFocus",
31
- "enableAutomaticFocus",
32
29
  "getGroupRootById",
33
30
  "isGroupItemFocused",
31
+ "recoverFocus",
32
+ "isCurrentFocusOnTheTopScreen",
34
33
  ];
35
34
 
36
35
  expectedMethods.forEach((method) => {
@@ -1,6 +1,15 @@
1
1
  import * as R from "ramda";
2
+ import {
3
+ isNilOrEmpty,
4
+ isNotNil,
5
+ } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
6
+
2
7
  import { Tree } from "./treeDataStructure/Tree";
3
- import { findFocusableNode, findPriorityItem } from "./treeDataStructure/Utils";
8
+ import {
9
+ findFocusableNode,
10
+ findPriorityItem,
11
+ haveSameParentBeforeRoot,
12
+ } from "./treeDataStructure/Utils";
4
13
  import { subscriber } from "../../functionUtils";
5
14
  import * as FOCUS_EVENTS from "./events";
6
15
  import { coreLogger } from "../../logger";
@@ -44,21 +53,9 @@ export const focusManager = (function () {
44
53
  // focusable node which currently holds the focus
45
54
  let currentFocusNode = null;
46
55
  let lastFocusedId = null;
47
- let initialFocusOnContent = null;
48
56
  let focusDisabled = false;
49
- let focusAutoEnabled = true;
50
57
  const eventHandler = subscriber();
51
58
 
52
- function disableAutomaticFocus() {
53
- logger.log({ message: "disableAutomaticFocus" });
54
- focusAutoEnabled = false;
55
- }
56
-
57
- function enableAutomaticFocus() {
58
- logger.log({ message: "focusManager: enableAutomaticFocus" });
59
- focusAutoEnabled = true;
60
- }
61
-
62
59
  function enableFocus() {
63
60
  focusDisabled = false;
64
61
  }
@@ -93,7 +90,7 @@ export const focusManager = (function () {
93
90
  !currentFocusable.isGroup &&
94
91
  currentFocusable.isMounted()
95
92
  ) {
96
- currentFocusable.blur(currentFocusable, direction);
93
+ currentFocusable.blur(direction);
97
94
  currentFocusable.hasLostFocus(getCurrentFocus(), direction);
98
95
  }
99
96
  }
@@ -224,40 +221,54 @@ export const focusManager = (function () {
224
221
  lastAddedParentNode?.children || focusableTree.root.children
225
222
  );
226
223
 
227
- // console.log("debug_2", "FM setInitialFocus", {
228
- // preferredFocus,
229
- // lastAddedParentNode,
230
- // focusableTree,
231
- // });
232
-
233
224
  logger.log({ message: "setInitialFocus", data: { preferredFocus } });
234
225
 
235
- if (preferredFocus && preferredFocus.length > 0) {
236
- const focusCandidate = preferredFocus[0];
237
- const focusableItem = focusCandidate.component;
238
-
239
- if (
240
- focusableItem &&
241
- !focusableItem.isGroup &&
242
- isFocusEnabled(focusableItem)
243
- ) {
244
- if (initialFocusOnContent) {
245
- blur();
246
- initialFocusOnContent = false;
247
- }
226
+ if (isNilOrEmpty(preferredFocus)) {
227
+ return { success: false };
228
+ }
248
229
 
249
- logger.log({
250
- message: "setInitialFocus – currentFocusNode",
251
- data: {
252
- focusableItem,
253
- },
254
- });
230
+ const focusCandidate = preferredFocus[0];
231
+ const focusableItem = focusCandidate.component;
255
232
 
256
- currentFocusNode = focusCandidate;
257
- focusableItem.willReceiveFocus(focusableItem, null);
258
- focusableItem && setFocus(focusCandidate.id, null);
259
- }
233
+ const currentFocusedId = R.path(["props", "id"], getCurrentFocus());
234
+ const nextFocusedId = R.path(["props", "id"], focusableItem);
235
+
236
+ if (
237
+ isNotNil(currentFocusedId) &&
238
+ isNotNil(nextFocusedId) &&
239
+ currentFocusedId === nextFocusedId
240
+ ) {
241
+ // we are going to move focus to the same node(already focused) -> nothing to do
242
+ return { success: true };
243
+ }
244
+
245
+ if (focusableItem?.isGroup) {
246
+ // focusCandidate is a FocusableGroup, we are not able to land focus on group(only on Focusable)
247
+ return { success: false };
260
248
  }
249
+
250
+ if (
251
+ focusableItem &&
252
+ !focusableItem.isGroup &&
253
+ isFocusEnabled(focusableItem)
254
+ ) {
255
+ blur();
256
+
257
+ logger.log({
258
+ message: "setInitialFocus – currentFocusNode",
259
+ data: {
260
+ focusableItem,
261
+ },
262
+ });
263
+
264
+ currentFocusNode = focusCandidate;
265
+ focusableItem.willReceiveFocus(focusableItem, null);
266
+ focusableItem && setFocus(focusCandidate.id, null);
267
+
268
+ return { success: true };
269
+ }
270
+
271
+ return { success: false };
261
272
  }
262
273
 
263
274
  function treeLoaded(val, node) {
@@ -267,17 +278,8 @@ export const focusManager = (function () {
267
278
  val,
268
279
  node,
269
280
  currentFocusNode,
270
- initialFocusOnContent,
271
281
  },
272
282
  });
273
-
274
- if ((val && !currentFocusNode) || initialFocusOnContent) {
275
- logger.log({ message: `focusAutoEnabled ${focusAutoEnabled}` });
276
-
277
- if (focusAutoEnabled) {
278
- setInitialFocus(initialFocusOnContent ? node : undefined);
279
- }
280
- }
281
283
  }
282
284
 
283
285
  /**
@@ -329,8 +331,6 @@ export const focusManager = (function () {
329
331
  * @returns {React.Component} the registered component
330
332
  */
331
333
  function registerGroup(id, component) {
332
- // console.log("debug_2", "FM registerGroup", { id, component });
333
-
334
334
  focusableTree.addNode({ id, component });
335
335
 
336
336
  return component;
@@ -346,8 +346,6 @@ export const focusManager = (function () {
346
346
  * @returns {React.Component} the registered component
347
347
  */
348
348
  function registerItem(id, component) {
349
- // console.log("debug_2", "FM registerItem", { id, component });
350
-
351
349
  focusableTree.addNode({ id, component });
352
350
 
353
351
  if (!currentFocusNode && id === lastFocusedId && !id.includes("menu")) {
@@ -364,8 +362,6 @@ export const focusManager = (function () {
364
362
  * @param {String} id of the group to unregister
365
363
  */
366
364
  function unregisterGroup(id) {
367
- // console.log("debug_2", "FM unregisterGroup", { id });
368
-
369
365
  focusableTree.findAndRemoveNode(id);
370
366
  }
371
367
 
@@ -377,8 +373,6 @@ export const focusManager = (function () {
377
373
  * @param {String} id of the Focusable to unregister
378
374
  */
379
375
  function unregisterItem(id) {
380
- // console.log("debug_2", "FM unregisterItem", { id });
381
-
382
376
  focusableTree.findAndRemoveNode(id);
383
377
 
384
378
  if (currentFocusNode && currentFocusNode.id === id) {
@@ -416,11 +410,21 @@ export const focusManager = (function () {
416
410
  group ? unregisterGroup(id) : unregisterItem(id);
417
411
  }
418
412
 
419
- function setupInitialFocus(id: string, focusOnContent?: boolean) {
420
- if (focusOnContent) {
421
- initialFocusOnContent = focusOnContent;
422
- } else {
423
- setFocus(id);
413
+ function isCurrentFocusOnTheTopScreen() {
414
+ const routes = R.pathOr([], ["root", "children"], focusableTree);
415
+
416
+ if (routes.length <= 1) {
417
+ // there is only one or zero routes
418
+ return true;
419
+ }
420
+
421
+ return haveSameParentBeforeRoot(currentFocusNode, R.last(routes));
422
+ }
423
+
424
+ function recoverFocus() {
425
+ if (!isCurrentFocusOnTheTopScreen()) {
426
+ // We've failed to set focused node on the new screen => run focus recovery
427
+ setInitialFocus();
424
428
  }
425
429
  }
426
430
 
@@ -430,19 +434,17 @@ export const focusManager = (function () {
430
434
  * @param {Object} direction of the scroll
431
435
  */
432
436
  function moveFocus(direction) {
437
+ if (!isCurrentFocusOnTheTopScreen()) {
438
+ // We are opening new screen. We need to ignore movement of focus.
439
+ return false;
440
+ }
441
+
433
442
  if (focusDisabled) return false;
434
443
 
435
444
  if (currentFocusNode) {
436
445
  currentFocusNode.component.willLoseFocus(currentFocusNode, direction);
437
446
  const nextFocusable = findFocusableNode(direction, currentFocusNode);
438
447
 
439
- // console.log("debug_2", "FM moveFocus", {
440
- // direction: direction?.value,
441
- // currentFocusNode,
442
- // focusableTree,
443
- // nextFocusable,
444
- // });
445
-
446
448
  if (nextFocusable?.component?.isMounted?.()) {
447
449
  const nextRect = nextFocusable.component.getRect();
448
450
 
@@ -465,8 +467,6 @@ export const focusManager = (function () {
465
467
  } else {
466
468
  currentFocusNode.component.failedLostFocus(currentFocusNode, direction);
467
469
  }
468
- } else {
469
- setInitialFocus();
470
470
  }
471
471
  }
472
472
 
@@ -526,7 +526,6 @@ export const focusManager = (function () {
526
526
  on,
527
527
  invokeHandler,
528
528
  removeHandler,
529
- setupInitialFocus,
530
529
  longPress,
531
530
  pressOut,
532
531
  enableFocus,
@@ -535,9 +534,9 @@ export const focusManager = (function () {
535
534
  isFocusDisabled,
536
535
  getCurrentFocusNode,
537
536
  focusableTree,
538
- disableAutomaticFocus,
539
- enableAutomaticFocus,
540
537
  getGroupRootById,
541
538
  isGroupItemFocused,
539
+ recoverFocus,
540
+ isCurrentFocusOnTheTopScreen,
542
541
  };
543
542
  })();
@@ -1,5 +1,7 @@
1
- import { allPass, pathOr, startsWith } from "ramda";
1
+ import { allPass, pathOr, startsWith, isNil } from "ramda";
2
2
  import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
3
+ import { isZero } from "@applicaster/zapp-react-native-utils/numberUtils";
4
+ import { QUICK_BRICK_NAVBAR } from "@applicaster/quick-brick-core/const";
3
5
 
4
6
  import { rejectItemByPath } from "../../../../objectUtils";
5
7
  import {
@@ -145,7 +147,7 @@ const hasComponent = (target) => !!target?.component;
145
147
 
146
148
  const hasComponentAndChildren = allPass([hasComponent, hasChildren]);
147
149
 
148
- const positiveOrZero = (value) => value >= 0;
150
+ const positiveOrZero = (value) => value > 0 || isZero(value);
149
151
 
150
152
  const excludeFromFocusSearching = pathOr(false, [
151
153
  "component",
@@ -153,10 +155,10 @@ const excludeFromFocusSearching = pathOr(false, [
153
155
  "excludeFromFocusSearching",
154
156
  ]);
155
157
 
156
- const isNavBar = (target) => startsWith("navbar", target?.id);
158
+ const isNavBar = (target) => startsWith(QUICK_BRICK_NAVBAR, target?.id);
157
159
 
158
160
  const ignoreMenuIfMultipleTargets = (nodes) => {
159
- if (nodes.length > 1 && nodes.map((n) => n?.target?.id).includes("navbar")) {
161
+ if (nodes.length > 1) {
160
162
  return nodes.filter((n) => !isNavBar(n?.target));
161
163
  }
162
164
 
@@ -224,17 +226,7 @@ export function getTargetInDirection(
224
226
  .sort((a, b) => a.distance - b.distance) // sorts the distances
225
227
  .filter(ignoreMenuIfMultipleTargets)[0]; // filters out multiple targets if necessary // gets the first element
226
228
 
227
- // console.log("debug_2", "FM getTargetInDirection", {
228
- // direction: direction?.value,
229
- // currentNode,
230
- // targetNodes,
231
- // targets,
232
- // comparisonRect,
233
- // distances,
234
- // match,
235
- // });
236
-
237
- return match.target;
229
+ return match?.target;
238
230
  }
239
231
 
240
232
  /**
@@ -325,3 +317,26 @@ export function hasPriorityInDirection(direction, component) {
325
317
 
326
318
  return (prioritiseFocusOn & mappedDirection) > 0;
327
319
  }
320
+
321
+ export const getNodeBeforeRoot = (node) => {
322
+ if (isNil(node)) {
323
+ return undefined;
324
+ }
325
+
326
+ if (isNil(node.parent)) {
327
+ return undefined;
328
+ }
329
+
330
+ if (node.parent.id === "root") {
331
+ return node;
332
+ }
333
+
334
+ return getNodeBeforeRoot(node.parent);
335
+ };
336
+
337
+ export const haveSameParentBeforeRoot = (currentNode, nextNode) => {
338
+ const routeForCurrentNode = getNodeBeforeRoot(currentNode);
339
+ const routeForNextNode = getNodeBeforeRoot(nextNode);
340
+
341
+ return routeForCurrentNode?.id === routeForNextNode?.id;
342
+ };
@@ -0,0 +1,100 @@
1
+ import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
2
+ import { find, last, startsWith, pathOr } from "ramda";
3
+ import {
4
+ QUICK_BRICK_NAVBAR,
5
+ QUICK_BRICK_CONTENT,
6
+ } from "@applicaster/quick-brick-core/const";
7
+
8
+ // run check each 300 ms
9
+ // run this check too often could lead to performance penalty on low-end devices
10
+ const HOW_OFTEN_TO_CHECK_CONDITION = 300; // ms
11
+
12
+ type Props = {
13
+ maxTimeout: number;
14
+ conditionFn: () => boolean;
15
+ };
16
+
17
+ export const waitUntil = ({ maxTimeout, conditionFn }: Props) => {
18
+ let interval;
19
+ let timeout;
20
+
21
+ return new Promise((resolve, reject) => {
22
+ // run interval check
23
+ interval = setInterval(() => {
24
+ if (conditionFn()) {
25
+ clearInterval(interval);
26
+ clearTimeout(timeout);
27
+
28
+ resolve(undefined);
29
+ }
30
+ }, HOW_OFTEN_TO_CHECK_CONDITION);
31
+
32
+ // max timeout when we need to stop interval check
33
+ timeout = setTimeout(() => {
34
+ clearInterval(interval);
35
+ clearTimeout(timeout);
36
+
37
+ const error = Error(
38
+ "Failed to wait satisfy condition. Max timeout is reached."
39
+ );
40
+
41
+ reject(error);
42
+ }, maxTimeout);
43
+ });
44
+ };
45
+
46
+ export const waitForActiveScreen = (currentRoute: string, focusableTree) => {
47
+ const isActiveScreenAvailableToFocus = (): boolean => {
48
+ const routes = focusableTree.root.children;
49
+
50
+ const route = find((route) => route.id === currentRoute, routes);
51
+
52
+ return isNotNil(route);
53
+ };
54
+
55
+ return waitUntil({
56
+ maxTimeout: HOW_OFTEN_TO_CHECK_CONDITION * 10, // ms
57
+ conditionFn: isActiveScreenAvailableToFocus,
58
+ });
59
+ };
60
+
61
+ const getActiveScreenChildren = (focusableTree) => {
62
+ return pathOr([], ["children"], last(focusableTree.root.children));
63
+ };
64
+
65
+ export const getContentNode = (focusableTree) => {
66
+ const content = find(
67
+ (child) =>
68
+ startsWith(QUICK_BRICK_CONTENT, child.id) ||
69
+ startsWith("player", child.id),
70
+ getActiveScreenChildren(focusableTree)
71
+ );
72
+
73
+ return content;
74
+ };
75
+
76
+ export const getNavbarNode = (focusableTree) => {
77
+ const navbar = find(
78
+ (child) => startsWith(QUICK_BRICK_NAVBAR, child.id),
79
+ getActiveScreenChildren(focusableTree)
80
+ );
81
+
82
+ return navbar;
83
+ };
84
+
85
+ export const waitForContent = (focusableTree) => {
86
+ const contentHasAnyChildren = (): boolean => {
87
+ const countOfChildren = pathOr(
88
+ 0,
89
+ ["children", "length"],
90
+ getContentNode(focusableTree)
91
+ );
92
+
93
+ return countOfChildren > 0;
94
+ };
95
+
96
+ return waitUntil({
97
+ maxTimeout: HOW_OFTEN_TO_CHECK_CONDITION * 10, // ms
98
+ conditionFn: contentHasAnyChildren,
99
+ });
100
+ };