@applicaster/zapp-react-native-utils 15.0.0-alpha.3564377339 → 15.0.0-alpha.3634430569

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 (127) hide show
  1. package/README.md +0 -6
  2. package/actionsExecutor/ActionExecutorContext.tsx +83 -6
  3. package/adsUtils/__tests__/createVMAP.test.ts +419 -0
  4. package/analyticsUtils/analyticsMapper.ts +10 -2
  5. package/appDataUtils/__tests__/urlScheme.test.ts +678 -0
  6. package/appUtils/HooksManager/__tests__/__snapshots__/hooksManager.test.js.snap +0 -188
  7. package/appUtils/HooksManager/__tests__/hooksManager.test.js +16 -2
  8. package/appUtils/HooksManager/index.ts +35 -0
  9. package/appUtils/RiverFocusManager/{index.js → index.ts} +25 -18
  10. package/appUtils/contextKeysManager/__tests__/getKeys/failure.test.ts +7 -2
  11. package/appUtils/contextKeysManager/__tests__/getKeys/success.test.ts +48 -0
  12. package/appUtils/contextKeysManager/contextResolver.ts +51 -22
  13. package/appUtils/contextKeysManager/index.ts +65 -10
  14. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +3 -0
  15. package/appUtils/focusManager/index.ios.ts +37 -4
  16. package/appUtils/focusManager/treeDataStructure/Tree/__tests__/Tree.test.js +46 -0
  17. package/appUtils/focusManager/treeDataStructure/Tree/index.js +18 -18
  18. package/appUtils/focusManagerAux/utils/index.ios.ts +122 -0
  19. package/appUtils/focusManagerAux/utils/index.ts +2 -4
  20. package/appUtils/focusManagerAux/utils/utils.ios.ts +155 -13
  21. package/appUtils/localizationsHelper.ts +4 -0
  22. package/appUtils/playerManager/index.ts +9 -0
  23. package/appUtils/playerManager/player.ts +1 -1
  24. package/appUtils/playerManager/playerNative.ts +2 -1
  25. package/appUtils/playerManager/usePlayer.tsx +5 -3
  26. package/cloudEventsUtils/__tests__/index.test.ts +529 -0
  27. package/cloudEventsUtils/index.ts +65 -1
  28. package/configurationUtils/__tests__/imageSrcFromMediaItem.test.ts +38 -0
  29. package/configurationUtils/index.ts +17 -11
  30. package/dateUtils/__tests__/dayjs.test.ts +330 -0
  31. package/enumUtils/__tests__/getEnumKeyByEnumValue.test.ts +207 -0
  32. package/errorUtils/__tests__/GeneralError.test.ts +97 -0
  33. package/errorUtils/__tests__/HttpStatusCode.test.ts +344 -0
  34. package/errorUtils/__tests__/MissingPluginError.test.ts +113 -0
  35. package/errorUtils/__tests__/NetworkError.test.ts +202 -0
  36. package/errorUtils/__tests__/getParsedResponse.test.ts +188 -0
  37. package/errorUtils/__tests__/invariant.test.ts +112 -0
  38. package/headersUtils/__tests__/headersUtils.test.js +11 -1
  39. package/headersUtils/index.ts +2 -1
  40. package/manifestUtils/_internals/__tests__/index.test.js +41 -0
  41. package/manifestUtils/_internals/index.js +33 -0
  42. package/manifestUtils/defaultManifestConfigurations/player.js +40 -10
  43. package/manifestUtils/fieldUtils/__tests__/fieldUtils.test.js +49 -0
  44. package/manifestUtils/fieldUtils/index.js +54 -0
  45. package/manifestUtils/index.js +2 -0
  46. package/manifestUtils/keys.js +228 -0
  47. package/manifestUtils/mobileAction/button/__tests__/mobileActionButton.test.js +168 -0
  48. package/manifestUtils/mobileAction/button/index.js +140 -0
  49. package/manifestUtils/mobileAction/container/__tests__/mobileActionButtonsContainer.test.js +102 -0
  50. package/manifestUtils/mobileAction/container/index.js +73 -0
  51. package/manifestUtils/mobileAction/groups/__tests__/buildMobileActionButtonGroups.test.js +127 -0
  52. package/manifestUtils/mobileAction/groups/defaults.js +75 -0
  53. package/manifestUtils/mobileAction/groups/index.js +80 -0
  54. package/manifestUtils/platformIsTV.js +13 -0
  55. package/navigationUtils/index.ts +15 -5
  56. package/numberUtils/__tests__/toNumber.test.ts +27 -0
  57. package/numberUtils/__tests__/toPositiveNumber.test.ts +193 -0
  58. package/numberUtils/index.ts +23 -1
  59. package/package.json +4 -4
  60. package/reactHooks/analytics/__tests__/useSendAnalyticsOnPress.test.ts +537 -0
  61. package/reactHooks/app/__tests__/useAppState.test.ts +1 -1
  62. package/reactHooks/autoscrolling/__tests__/useTrackCurrentAutoScrollingElement.test.ts +1 -1
  63. package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +1 -2
  64. package/reactHooks/cell-click/__tests__/index.test.js +1 -3
  65. package/reactHooks/cell-click/index.ts +2 -1
  66. package/reactHooks/configuration/__tests__/index.test.tsx +1 -1
  67. package/reactHooks/connection/__tests__/index.test.js +1 -1
  68. package/reactHooks/dev/__tests__/useReRenderLog.test.ts +188 -0
  69. package/reactHooks/device/useIsTablet.tsx +14 -19
  70. package/reactHooks/events/index.ts +20 -0
  71. package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +32 -23
  72. package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +19 -19
  73. package/reactHooks/feed/__tests__/useEntryScreenId.test.tsx +1 -1
  74. package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +42 -30
  75. package/reactHooks/feed/__tests__/useInflatedUrl.test.tsx +1 -1
  76. package/reactHooks/feed/index.ts +0 -2
  77. package/reactHooks/feed/useInflatedUrl.ts +1 -1
  78. package/reactHooks/hookModal/hooks/useHookModalScreenData.ts +12 -8
  79. package/reactHooks/index.ts +2 -0
  80. package/reactHooks/layout/__tests__/index.test.tsx +1 -1
  81. package/reactHooks/layout/__tests__/useLayoutVersion.test.tsx +1 -1
  82. package/reactHooks/navigation/__tests__/index.test.tsx +40 -9
  83. package/reactHooks/navigation/index.ts +19 -4
  84. package/reactHooks/navigation/useRoute.ts +3 -1
  85. package/reactHooks/player/__tests__/useAutoSeek._test.tsx +1 -1
  86. package/reactHooks/player/__tests__/useTapSeek._test.ts +1 -1
  87. package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +1 -1
  88. package/reactHooks/resolvers/__tests__/useComponentResolver.test.tsx +1 -1
  89. package/reactHooks/resolvers/useComponentResolver.ts +11 -1
  90. package/reactHooks/screen/__tests__/useCurrentScreenData.test.tsx +2 -2
  91. package/reactHooks/screen/__tests__/useScreenBackgroundColor.test.tsx +1 -1
  92. package/reactHooks/screen/__tests__/useScreenData.test.tsx +1 -1
  93. package/reactHooks/screen/__tests__/useTargetScreenData.test.tsx +2 -2
  94. package/reactHooks/state/__tests__/useComponentScreenState.test.ts +246 -0
  95. package/reactHooks/state/index.ts +2 -0
  96. package/reactHooks/state/useComponentScreenState.ts +45 -0
  97. package/reactHooks/state/useRefWithInitialValue.ts +10 -0
  98. package/reactHooks/ui/__tests__/useFadeOutWhenBlurred.test.ts +580 -0
  99. package/reactHooks/utils/__tests__/index.test.js +1 -1
  100. package/rectUtils/__tests__/index.test.ts +549 -0
  101. package/rectUtils/index.ts +2 -2
  102. package/refreshUtils/RefreshCoordinator/__tests__/refreshCoordinator.test.ts +161 -0
  103. package/refreshUtils/RefreshCoordinator/index.ts +216 -0
  104. package/refreshUtils/RefreshCoordinator/utils/__tests__/getDataRefreshConfig.test.ts +104 -0
  105. package/refreshUtils/RefreshCoordinator/utils/index.ts +29 -0
  106. package/screenPickerUtils/__tests__/index.test.ts +333 -0
  107. package/screenPickerUtils/index.ts +5 -0
  108. package/screenState/__tests__/index.test.ts +1 -1
  109. package/screenUtils/index.ts +3 -0
  110. package/searchUtils/const.ts +7 -0
  111. package/searchUtils/index.ts +3 -0
  112. package/services/storageServiceSync.web.ts +1 -1
  113. package/stringUtils/index.ts +1 -1
  114. package/testUtils/index.tsx +1 -1
  115. package/time/__tests__/BackgroundTimer.test.ts +156 -0
  116. package/time/__tests__/Timer.test.ts +236 -0
  117. package/typeGuards/__tests__/isString.test.ts +21 -0
  118. package/typeGuards/index.ts +4 -0
  119. package/utils/__tests__/clone.test.ts +158 -0
  120. package/utils/__tests__/mergeRight.test.ts +48 -0
  121. package/utils/__tests__/path.test.ts +7 -0
  122. package/utils/clone.ts +7 -0
  123. package/utils/index.ts +5 -1
  124. package/utils/mergeRight.ts +5 -0
  125. package/reactHooks/componentsMap/index.ts +0 -55
  126. package/reactHooks/feed/__tests__/useFeedRefresh.test.tsx +0 -75
  127. package/reactHooks/feed/useFeedRefresh.tsx +0 -65
@@ -71,6 +71,9 @@ exports[`focusManagerIOS should be defined 1`] = `
71
71
  "invokeHandler": [Function],
72
72
  "isChildOf": [Function],
73
73
  "isFocusOn": [Function],
74
+ "isFocusOnContent": [Function],
75
+ "isFocusOnMenu": [Function],
76
+ "isFocusOnTabsScreenContent": [Function],
74
77
  "isGroupItemFocused": [Function],
75
78
  "moveFocus": [Function],
76
79
  "on": [Function],
@@ -4,7 +4,11 @@ import * as R from "ramda";
4
4
  import {
5
5
  isCurrentFocusOn,
6
6
  isChildOf as isChildOfUtils,
7
- } from "../focusManagerAux/utils";
7
+ isPartOfMenu,
8
+ isPartOfContent,
9
+ isPartOfTabsScreenContent,
10
+ } from "../focusManagerAux/utils/index.ios";
11
+
8
12
  import { Tree } from "./treeDataStructure/Tree";
9
13
  import { findFocusableNode } from "./treeDataStructure/Utils";
10
14
  import { subscriber } from "../../functionUtils";
@@ -194,9 +198,9 @@ export const focusManager = (function () {
194
198
  registerItem(id, component);
195
199
  }
196
200
 
197
- const parentId = component?.props?.groupId;
201
+ const groupId = component?.props?.groupId;
198
202
 
199
- emitRegistered({ id, parentId, isGroup });
203
+ emitRegistered({ id, groupId, isGroup });
200
204
  }
201
205
 
202
206
  function unregister(id, { group = false } = {}) {
@@ -279,7 +283,9 @@ export const focusManager = (function () {
279
283
  function setFocus(
280
284
  id: string,
281
285
  direction?: FocusManager.IOS.Direction,
282
- options?: Partial<{ groupFocusedChanged: boolean }>,
286
+ options?: Partial<{
287
+ groupFocusedChanged: boolean;
288
+ }>,
283
289
  callback?: any
284
290
  ) {
285
291
  blur(direction);
@@ -418,6 +424,30 @@ export const focusManager = (function () {
418
424
  return id && isCurrentFocusOn(id, currentFocusNode);
419
425
  }
420
426
 
427
+ function isFocusOnMenu(): boolean {
428
+ const currentFocusable = getCurrentFocus();
429
+
430
+ return isPartOfMenu(focusableTree, currentFocusable?.props?.id);
431
+ }
432
+
433
+ function isFocusOnContent(): boolean {
434
+ const currentFocusable = getCurrentFocus();
435
+
436
+ return isPartOfContent(focusableTree, currentFocusable?.props?.id);
437
+ }
438
+
439
+ function isFocusOnTabsScreenContent(
440
+ screenPickerContentContainerId: string
441
+ ): boolean {
442
+ const currentFocusable = getCurrentFocus();
443
+
444
+ return isPartOfTabsScreenContent(
445
+ focusableTree,
446
+ screenPickerContentContainerId,
447
+ currentFocusable?.props?.id
448
+ );
449
+ }
450
+
421
451
  function isChildOf(childId, parentId): boolean {
422
452
  return isChildOfUtils(focusableTree, childId, parentId);
423
453
  }
@@ -444,6 +474,9 @@ export const focusManager = (function () {
444
474
  isGroupItemFocused,
445
475
  getPreferredFocusChild,
446
476
  isFocusOn,
477
+ isFocusOnMenu,
478
+ isFocusOnContent,
479
+ isFocusOnTabsScreenContent,
447
480
  isChildOf,
448
481
  };
449
482
  })();
@@ -373,3 +373,49 @@ describe("addNode", () => {
373
373
  checkParents(tree.root);
374
374
  });
375
375
  });
376
+
377
+ describe("findInTree", () => {
378
+ function createNode(id, children) {
379
+ return {
380
+ id,
381
+ children,
382
+ };
383
+ }
384
+
385
+ it("returns a direct child match from root children", () => {
386
+ const tree = new Tree(treeLoaded);
387
+ const direct = createNode("direct-node");
388
+
389
+ tree.root.children = [direct];
390
+
391
+ expect(tree.findInTree("direct-node")).toEqual(direct);
392
+ });
393
+
394
+ it("returns a nested descendant match", () => {
395
+ const tree = new Tree(treeLoaded);
396
+ const nested = createNode("nested-node");
397
+ const intermediate = createNode("intermediate-node", [nested]);
398
+ const rootNode = createNode("root-node", [intermediate]);
399
+
400
+ tree.root.children = [rootNode];
401
+
402
+ expect(tree.findInTree("nested-node")).toEqual(nested);
403
+ });
404
+
405
+ it("returns null when node id does not exist", () => {
406
+ const tree = new Tree(treeLoaded);
407
+ const leaf = createNode("leaf-node");
408
+ const rootNode = createNode("root-node", [leaf]);
409
+
410
+ tree.root.children = [rootNode];
411
+
412
+ expect(tree.findInTree("missing-node")).toEqual(null);
413
+ });
414
+
415
+ it("returns null when tree has no children", () => {
416
+ const tree = new Tree(treeLoaded);
417
+ tree.root.children = null;
418
+
419
+ expect(tree.findInTree("any-node")).toEqual(null);
420
+ });
421
+ });
@@ -205,31 +205,31 @@ export class Tree {
205
205
  * @returns founded node or null
206
206
  */
207
207
  findInTree(id) {
208
- const retVal = null;
209
-
210
- return this.findInArray(id, this.root.children, retVal);
208
+ return this.findInArray(id, this.root.children);
211
209
  }
212
210
 
213
- findInArray(id, children, retVal) {
214
- if (!retVal && children) {
215
- retVal = children.find((obj) => obj.id === id);
211
+ findInArray(id, children) {
212
+ if (!children) {
213
+ return null;
214
+ }
216
215
 
217
- if (!retVal) {
218
- children.forEach((child) => {
219
- if (child.children) {
220
- retVal = this.findInArray(id, child.children, retVal);
216
+ const directMatch = children.find((obj) => obj.id === id);
221
217
 
222
- if (retVal) {
223
- return retVal;
224
- }
225
- }
226
- });
227
- } else {
228
- return retVal;
218
+ if (directMatch) {
219
+ return directMatch;
220
+ }
221
+
222
+ for (const child of children) {
223
+ if (child.children) {
224
+ const nestedMatch = this.findInArray(id, child.children);
225
+
226
+ if (nestedMatch) {
227
+ return nestedMatch;
228
+ }
229
229
  }
230
230
  }
231
231
 
232
- return retVal;
232
+ return null;
233
233
  }
234
234
 
235
235
  /**
@@ -0,0 +1,122 @@
1
+ import { isNil, startsWith } from "@applicaster/zapp-react-native-utils/utils";
2
+
3
+ import {
4
+ QUICK_BRICK_CONTENT,
5
+ QUICK_BRICK_NAVBAR,
6
+ } from "@applicaster/quick-brick-core/const";
7
+
8
+ const isNavBar = (node) => startsWith(QUICK_BRICK_NAVBAR, node?.id);
9
+ const isContent = (node) => startsWith(QUICK_BRICK_CONTENT, node?.id);
10
+ const isRoot = (node) => node?.id === "root";
11
+
12
+ export const isPartOfTabsScreenContent = (
13
+ focusableTree,
14
+ screenPickerContentContainerId,
15
+ id
16
+ ) => {
17
+ const node = focusableTree.findInTree(id);
18
+
19
+ if (isNil(node)) {
20
+ return false;
21
+ }
22
+
23
+ if (isRoot(node)) {
24
+ return false;
25
+ }
26
+
27
+ if (isNavBar(node)) {
28
+ return false;
29
+ }
30
+
31
+ if (isContent(node)) {
32
+ return false;
33
+ }
34
+
35
+ if (node?.id === screenPickerContentContainerId) {
36
+ return true;
37
+ }
38
+
39
+ return isPartOfTabsScreenContent(
40
+ focusableTree,
41
+ screenPickerContentContainerId,
42
+ node.parent?.id
43
+ );
44
+ };
45
+
46
+ export const isPartOfMenu = (focusableTree, id): boolean => {
47
+ const node = focusableTree.findInTree(id);
48
+
49
+ if (isNil(node)) {
50
+ return false;
51
+ }
52
+
53
+ if (isRoot(node)) {
54
+ return false;
55
+ }
56
+
57
+ if (isNavBar(node)) {
58
+ return true;
59
+ }
60
+
61
+ if (isContent(node)) {
62
+ return false;
63
+ }
64
+
65
+ return isPartOfMenu(focusableTree, node.parent?.id);
66
+ };
67
+
68
+ export const isPartOfContent = (focusableTree, id) => {
69
+ const node = focusableTree.findInTree(id);
70
+
71
+ if (isNil(node)) {
72
+ return false;
73
+ }
74
+
75
+ if (isRoot(node)) {
76
+ return false;
77
+ }
78
+
79
+ if (isNavBar(node)) {
80
+ return false;
81
+ }
82
+
83
+ if (isContent(node)) {
84
+ return true;
85
+ }
86
+
87
+ return isPartOfContent(focusableTree, node.parent?.id);
88
+ };
89
+
90
+ export const isCurrentFocusOn = (id, node) => {
91
+ if (!node) {
92
+ return false;
93
+ }
94
+
95
+ if (isRoot(node)) {
96
+ return false;
97
+ }
98
+
99
+ if (node?.id === id) {
100
+ return true;
101
+ }
102
+
103
+ return isCurrentFocusOn(id, node.parent);
104
+ };
105
+
106
+ export const isChildOf = (focusableTree, childId, parentId) => {
107
+ if (isNil(childId) || isNil(parentId)) {
108
+ return false;
109
+ }
110
+
111
+ const childNode = focusableTree.findInTree(childId);
112
+
113
+ if (isNil(childNode)) {
114
+ return false;
115
+ }
116
+
117
+ if (childNode.parent?.id === parentId) {
118
+ return true;
119
+ }
120
+
121
+ return isChildOf(focusableTree, childNode.parent?.id, parentId);
122
+ };
@@ -13,7 +13,7 @@ import {
13
13
 
14
14
  import {
15
15
  getFocusableId,
16
- SCREEN_PICKER_CONTAINER,
16
+ isTabsScreenContentContainerId,
17
17
  } from "@applicaster/zapp-react-native-utils/screenPickerUtils";
18
18
 
19
19
  // run check each 300 ms
@@ -24,8 +24,6 @@ const isTopMenu = (node) => startsWith(QUICK_BRICK_NAVBAR, node?.id);
24
24
  const isContent = (node) => startsWith(QUICK_BRICK_CONTENT, node?.id);
25
25
  const isRoot = (node) => node?.id === "root";
26
26
 
27
- const isScrenPicker = (node) => startsWith(SCREEN_PICKER_CONTAINER, node?.id);
28
-
29
27
  type Props = {
30
28
  maxTimeout: number;
31
29
  conditionFn: () => boolean;
@@ -136,7 +134,7 @@ export const isTabsScreenOnContentFocused = (node) => {
136
134
  return false;
137
135
  }
138
136
 
139
- if (isScrenPicker(node)) {
137
+ if (isTabsScreenContentContainerId(node?.id)) {
140
138
  return true;
141
139
  }
142
140
 
@@ -1,7 +1,9 @@
1
1
  import { ReplaySubject, Subject } from "rxjs";
2
- import { filter, switchMap, take } from "rxjs/operators";
2
+ import { filter, switchMap, take, withLatestFrom, map } from "rxjs/operators";
3
+
3
4
  import { BUTTON_PREFIX } from "@applicaster/zapp-react-native-ui-components/Components/MasterCell/DefaultComponents/tv/TvActionButtons/const";
4
5
  import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager/index.ios";
6
+ import { isPartOfMenu, isPartOfContent } from "./index.ios";
5
7
 
6
8
  type FocusableID = string;
7
9
  type RegistrationEvent = {
@@ -11,13 +13,23 @@ type RegistrationEvent = {
11
13
 
12
14
  let focusableViewRegistrationSubject$ = new Subject<{
13
15
  id: FocusableID;
14
- parentId: FocusableID;
16
+ groupId: FocusableID;
15
17
  }>();
16
18
 
17
19
  let focusableGroupRegistrationSubject$ = new ReplaySubject<{
18
20
  id: FocusableID;
19
21
  }>();
20
22
 
23
+ let focusableNativeViewRegistrationSubject$ = new Subject<{
24
+ id: FocusableID;
25
+ groupId: FocusableID;
26
+ }>();
27
+
28
+ let focusableNativeGroupRegistrationSubject$ = new ReplaySubject<{
29
+ id: FocusableID;
30
+ groupId: FocusableID;
31
+ }>();
32
+
21
33
  const isFocusableButton = (id: Option<FocusableID>): boolean =>
22
34
  id && id.includes?.(BUTTON_PREFIX);
23
35
 
@@ -41,35 +53,93 @@ export const resetFocusableRegistration = () => {
41
53
  focusableGroupRegistrationSubject$.complete();
42
54
  }
43
55
 
56
+ if (!focusableNativeViewRegistrationSubject$.closed) {
57
+ focusableNativeViewRegistrationSubject$.complete();
58
+ }
59
+
60
+ if (!focusableNativeGroupRegistrationSubject$.closed) {
61
+ focusableNativeGroupRegistrationSubject$.complete();
62
+ }
63
+
44
64
  focusableViewRegistrationSubject$ = new Subject<{
45
65
  id: FocusableID;
46
- parentId: FocusableID;
66
+ groupId: FocusableID;
47
67
  }>();
48
68
 
49
69
  focusableGroupRegistrationSubject$ = new ReplaySubject<{
50
70
  id: FocusableID;
51
71
  }>();
72
+
73
+ focusableNativeViewRegistrationSubject$ = new Subject<{
74
+ id: FocusableID;
75
+ groupId: FocusableID;
76
+ }>();
77
+
78
+ focusableNativeGroupRegistrationSubject$ = new ReplaySubject<{
79
+ id: FocusableID;
80
+ groupId: FocusableID;
81
+ }>();
52
82
  };
53
83
 
54
- export const firstFocusableViewRegistrationFactory = () =>
84
+ const focusableNativeViewRegistration = ({ focusableView, focusableGroup }) => {
85
+ return focusableNativeViewRegistrationSubject$.pipe(
86
+ filter(
87
+ (focusableNativeView) => focusableNativeView.id === focusableView.id
88
+ ),
89
+ take(1),
90
+ switchMap((focusableNativeView) =>
91
+ // start waiting registration of its parent FocusableNativeGroup
92
+ focusableNativeGroupRegistrationSubject$.pipe(
93
+ filter(
94
+ (focusableNativeGroup) =>
95
+ focusableNativeGroup.id === focusableNativeView.groupId
96
+ ),
97
+ take(1),
98
+ map((focusableNativeGroup) => ({
99
+ focusableNativeGroup,
100
+ focusableNativeView,
101
+ focusableView,
102
+ focusableGroup,
103
+ }))
104
+ )
105
+ )
106
+ );
107
+ };
108
+
109
+ export const firstFocusableViewInContentRegistrationFactory = () =>
55
110
  focusableViewRegistrationSubject$.pipe(
56
- take(1), // we care about only first FocusableView registration
57
- switchMap(({ parentId }) =>
58
- // start waiting registration of its parent
111
+ switchMap((focusableView) =>
112
+ // start waiting registration of its parent FocusableGroup
59
113
  focusableGroupRegistrationSubject$.pipe(
60
- filter(({ id }) => id === parentId)
114
+ filter((focusableGroup) => focusableGroup.id === focusableView.groupId),
115
+ take(1),
116
+ map((focusableGroup) => ({
117
+ focusableView,
118
+ focusableGroup,
119
+ }))
61
120
  )
62
121
  ),
63
- take(1)
122
+ // start waiting registration for FocusableNativeView and its parent FocusableNativeGroup
123
+ switchMap(({ focusableView, focusableGroup }) =>
124
+ focusableNativeViewRegistration({
125
+ focusableView,
126
+ focusableGroup,
127
+ })
128
+ ),
129
+ filter(({ focusableView }) =>
130
+ isPartOfContent(focusManager.focusableTree, focusableView.id)
131
+ ),
132
+ take(1) // we care about only first FocusableView registration
64
133
  );
65
134
 
135
+ // registration on RN level(into RN focusManager)
66
136
  export const emitRegistered = ({
67
137
  id,
68
- parentId,
138
+ groupId,
69
139
  isGroup,
70
140
  }: {
71
141
  id: Option<FocusableID>;
72
- parentId: Option<FocusableID>;
142
+ groupId: Option<FocusableID>;
73
143
  isGroup: boolean;
74
144
  }): void => {
75
145
  if (isFocusableButton(id)) {
@@ -80,13 +150,85 @@ export const emitRegistered = ({
80
150
  focusableGroupRegistrationSubject$.next({ id });
81
151
  }
82
152
 
83
- if (!isGroup && id && parentId) {
84
- focusableViewRegistrationSubject$.next({ id, parentId });
153
+ if (!isGroup && id && groupId) {
154
+ focusableViewRegistrationSubject$.next({ id, groupId });
85
155
  }
86
156
  };
87
157
 
158
+ // unregistration on RN level(into RN focusManager)
88
159
  export const emitUnregistered = (id: Option<FocusableID>): void => {
89
160
  if (isFocusableButton(id)) {
90
161
  registeredSubject$.next({ id, registered: false });
91
162
  }
92
163
  };
164
+
165
+ // registration focusableNativeView and focusableNativeGroup
166
+ export const emitNativeRegistered = ({
167
+ id,
168
+ groupId,
169
+ isGroup,
170
+ }: {
171
+ id: Option<FocusableID>;
172
+ groupId: Option<FocusableID>;
173
+ isGroup: boolean;
174
+ }): void => {
175
+ if (!isGroup && id && groupId) {
176
+ focusableNativeViewRegistrationSubject$.next({ id, groupId });
177
+ }
178
+
179
+ if (isGroup && id && groupId) {
180
+ focusableNativeGroupRegistrationSubject$.next({ id, groupId });
181
+ }
182
+ };
183
+
184
+ // /////
185
+
186
+ const focusedSubject$ = new Subject<FocusableID>();
187
+
188
+ const focused$ = focusedSubject$.asObservable();
189
+
190
+ export const emitFocused = (id: FocusableID): void => {
191
+ focusedSubject$.next(id);
192
+ };
193
+
194
+ export const topMenuItemFocused$ = focused$.pipe(
195
+ filter((id) => id && isPartOfMenu(focusManager.focusableTree, id))
196
+ );
197
+
198
+ export const contentFocused$ = focused$.pipe(
199
+ filter((id) => {
200
+ const isContent = isPartOfContent(focusManager.focusableTree, id);
201
+
202
+ return id && isContent;
203
+ })
204
+ );
205
+
206
+ const createFocusableRegistry = () => {
207
+ const subject$ = new ReplaySubject<FocusableID | undefined>(1);
208
+
209
+ return {
210
+ observable$: subject$.asObservable(),
211
+ register: (id: FocusableID) => {
212
+ // save focusable_id on registration
213
+ subject$.next(id);
214
+ },
215
+ unregister: () => {
216
+ // reset focusable_id on unregistration
217
+ subject$.next(undefined);
218
+ },
219
+ };
220
+ };
221
+
222
+ /// HOME_TOP_MENU_ITEM
223
+ export const HomeTopMenuItemRegistry = createFocusableRegistry();
224
+
225
+ export const homeTopMenuItemFocused$ = topMenuItemFocused$.pipe(
226
+ withLatestFrom(HomeTopMenuItemRegistry.observable$),
227
+ filter(([id, homeId]) => id === homeId)
228
+ );
229
+
230
+ /// SCREEN_PICKER
231
+ export const ScreenPickerContentContainerRegistry = createFocusableRegistry();
232
+
233
+ /// SEARCH_INPUT
234
+ export const SearchInputRegistry = createFocusableRegistry();
@@ -85,6 +85,10 @@ export const toDayJSLocaleMap = (code) => {
85
85
  tt: null,
86
86
  "es-LA": "es",
87
87
  "en-UK": "en-gb",
88
+ "en-ZA": null,
89
+ "en-NG": null,
90
+ "en-ZW": null,
91
+ "en-ZM": null,
88
92
  };
89
93
 
90
94
  if (map[code] === null) {
@@ -33,6 +33,7 @@ function deprecationWarning(method) {
33
33
  export interface PlayerLifecycleListener {
34
34
  onRegistered?: (player: Player) => void;
35
35
  onUnRegistered?: (player: Player) => void;
36
+ onCastingChanged?: (isCasting: boolean) => void;
36
37
  }
37
38
 
38
39
  type PlayersMap = {
@@ -253,10 +254,18 @@ export class PlayerManager {
253
254
 
254
255
  public registerCastingReceiver(receiver: Player) {
255
256
  this.castingReceiver = receiver;
257
+
258
+ this.lifecycleListeners.forEach((listener) => {
259
+ listener.onCastingChanged?.(true);
260
+ });
256
261
  }
257
262
 
258
263
  public unregisterCastingReceiver() {
259
264
  this.castingReceiver = null;
265
+
266
+ this.lifecycleListeners.forEach((listener) => {
267
+ listener.onCastingChanged?.(false);
268
+ });
260
269
  }
261
270
 
262
271
  /**
@@ -258,7 +258,7 @@ export class Player {
258
258
  };
259
259
 
260
260
  isAd = () => {
261
- return !!this.playerState.adState || this.playerState.isInAdBreak;
261
+ return !!this.playerState.adState || !!this.playerState.isInAdBreak;
262
262
  };
263
263
 
264
264
  isSeeking = () => {
@@ -229,8 +229,9 @@ export class PlayerNative extends Player {
229
229
  };
230
230
 
231
231
  closeNativePlayer = () => {
232
- // TODO: Delete does not work
232
+ // TODO: Delete, does not work (component is null)
233
233
  this.currentPlayerComponent()?.closeNativePlayer?.();
234
+ this.getPlayerModule()?.stopBackgroundPlayback?.();
234
235
  };
235
236
 
236
237
  togglePlayPause = () => {
@@ -22,7 +22,6 @@ export const usePlayer = (playerId?: string): Player => {
22
22
 
23
23
  const isCasting = playerManager.isCasting();
24
24
 
25
- // TODO: We need to prerender when we start/stop casting, fix this
26
25
  const getPlayer = (playerId, isCasting) =>
27
26
  getControlledPlayer(playerId, isCasting);
28
27
 
@@ -46,7 +45,7 @@ export const usePlayer = (playerId?: string): Player => {
46
45
  return playerManager.addLifecycleListener({
47
46
  onRegistered: (player) => {
48
47
  if (playerIdToUse === player.playerId) {
49
- setPlayer(getPlayer(playerIdToUse, isCasting));
48
+ setPlayer(getPlayer(playerIdToUse, playerManager.isCasting()));
50
49
  }
51
50
  },
52
51
  onUnRegistered: (player) => {
@@ -54,8 +53,11 @@ export const usePlayer = (playerId?: string): Player => {
54
53
  setPlayer(null);
55
54
  }
56
55
  },
56
+ onCastingChanged: () => {
57
+ setPlayer(getPlayer(playerIdToUse, playerManager.isCasting()));
58
+ },
57
59
  } as PlayerLifecycleListener);
58
- }, [playerIdToUse, isCasting]);
60
+ }, [playerIdToUse]);
59
61
 
60
62
  return player;
61
63
  };