@applicaster/zapp-react-native-utils 12.0.0-alpha.4802689107 → 12.0.0-alpha.4837064762

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.
@@ -1,4 +1,6 @@
1
1
  import * as R from "ramda";
2
+ import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
3
+
2
4
  import { Tree } from "./treeDataStructure/Tree";
3
5
  import { findFocusableNode, findPriorityItem } from "./treeDataStructure/Utils";
4
6
  import { subscriber } from "../../functionUtils";
@@ -17,6 +19,22 @@ const isFocusEnabled = (focusableItem): boolean => {
17
19
  return true;
18
20
  };
19
21
 
22
+ const waitUntil = (conditionFn) => {
23
+ let interval;
24
+ return new Promise((resolve, reject) => {
25
+ interval = setInterval(() => {
26
+ if (conditionFn()) {
27
+ clearInterval(interval);
28
+ resolve(undefined);
29
+ }
30
+ }, 1000);
31
+
32
+ setTimeout(() => {
33
+ reject();
34
+ }, 2500);
35
+ });
36
+ };
37
+
20
38
  /**
21
39
  * The focusManager is meant to keep references of focusables elements in the UI, and provide
22
40
  * ways to assign the focus to one of the focusables.
@@ -46,7 +64,7 @@ export const focusManager = (function () {
46
64
  let lastFocusedId = null;
47
65
  let initialFocusOnContent = null;
48
66
  let focusDisabled = false;
49
- let focusAutoEnabled = true;
67
+ let focusAutoEnabled = false;
50
68
  const eventHandler = subscriber();
51
69
 
52
70
  function disableAutomaticFocus() {
@@ -93,7 +111,7 @@ export const focusManager = (function () {
93
111
  !currentFocusable.isGroup &&
94
112
  currentFocusable.isMounted()
95
113
  ) {
96
- currentFocusable.blur(currentFocusable, direction);
114
+ currentFocusable.blur(direction);
97
115
  currentFocusable.hasLostFocus(getCurrentFocus(), direction);
98
116
  }
99
117
  }
@@ -226,31 +244,41 @@ export const focusManager = (function () {
226
244
 
227
245
  logger.log({ message: "setInitialFocus", data: { preferredFocus } });
228
246
 
229
- if (preferredFocus && preferredFocus.length > 0) {
230
- const focusCandidate = preferredFocus[0];
231
- const focusableItem = focusCandidate.component;
232
-
233
- if (
234
- focusableItem &&
235
- !focusableItem.isGroup &&
236
- isFocusEnabled(focusableItem)
237
- ) {
238
- if (initialFocusOnContent) {
239
- blur();
240
- initialFocusOnContent = false;
241
- }
247
+ if (isNilOrEmpty(preferredFocus)) {
248
+ return;
249
+ }
250
+
251
+ const focusCandidate = preferredFocus[0];
252
+ const focusableItem = focusCandidate.component;
242
253
 
243
- logger.log({
244
- message: "setInitialFocus – currentFocusNode",
245
- data: {
246
- focusableItem,
247
- },
248
- });
254
+ const currentFocusedId = R.path(["props", "id"], getCurrentFocus());
255
+ const nextFocusedId = R.path(["props", "id"], focusableItem);
249
256
 
250
- currentFocusNode = focusCandidate;
251
- focusableItem.willReceiveFocus(focusableItem, null);
252
- focusableItem && setFocus(focusCandidate.id, null);
257
+ if (currentFocusedId === nextFocusedId) {
258
+ // we are going to move focus to the same node(already focused) -> nothing to do
259
+ return;
260
+ }
261
+
262
+ if (
263
+ focusableItem &&
264
+ !focusableItem.isGroup &&
265
+ isFocusEnabled(focusableItem)
266
+ ) {
267
+ if (initialFocusOnContent) {
268
+ blur();
269
+ initialFocusOnContent = false;
253
270
  }
271
+
272
+ logger.log({
273
+ message: "setInitialFocus – currentFocusNode",
274
+ data: {
275
+ focusableItem,
276
+ },
277
+ });
278
+
279
+ currentFocusNode = focusCandidate;
280
+ focusableItem.willReceiveFocus(focusableItem, null);
281
+ focusableItem && setFocus(focusCandidate.id, null);
254
282
  }
255
283
  }
256
284
 
@@ -323,6 +351,8 @@ export const focusManager = (function () {
323
351
  * @returns {React.Component} the registered component
324
352
  */
325
353
  function registerGroup(id, component) {
354
+ // console.log("debug_2", "FM registerGroup", { id, component });
355
+
326
356
  focusableTree.addNode({ id, component });
327
357
 
328
358
  return component;
@@ -338,6 +368,8 @@ export const focusManager = (function () {
338
368
  * @returns {React.Component} the registered component
339
369
  */
340
370
  function registerItem(id, component) {
371
+ // console.log("debug_2", "FM registerItem", { id, component });
372
+
341
373
  focusableTree.addNode({ id, component });
342
374
 
343
375
  if (!currentFocusNode && id === lastFocusedId && !id.includes("menu")) {
@@ -354,6 +386,8 @@ export const focusManager = (function () {
354
386
  * @param {String} id of the group to unregister
355
387
  */
356
388
  function unregisterGroup(id) {
389
+ // console.log("debug_2", "FM unregisterGroup", { id });
390
+
357
391
  focusableTree.findAndRemoveNode(id);
358
392
  }
359
393
 
@@ -365,6 +399,8 @@ export const focusManager = (function () {
365
399
  * @param {String} id of the Focusable to unregister
366
400
  */
367
401
  function unregisterItem(id) {
402
+ // console.log("debug_2", "FM unregisterItem", { id });
403
+
368
404
  focusableTree.findAndRemoveNode(id);
369
405
 
370
406
  if (currentFocusNode && currentFocusNode.id === id) {
@@ -403,6 +439,8 @@ export const focusManager = (function () {
403
439
  }
404
440
 
405
441
  function setupInitialFocus(id: string, focusOnContent?: boolean) {
442
+ console.log("debug_2", "FM - setupInitialFocus", { id, focusOnContent });
443
+
406
444
  if (focusOnContent) {
407
445
  initialFocusOnContent = focusOnContent;
408
446
  } else {
@@ -410,6 +448,62 @@ export const focusManager = (function () {
410
448
  }
411
449
  }
412
450
 
451
+ function setFocusOnMenu() {
452
+ const activeScreen = R.last(focusableTree.root.children);
453
+ const navbar = R.find(
454
+ (child) => R.startsWith("navbar", child.id),
455
+ activeScreen.children
456
+ );
457
+
458
+ const selectedMenuItemId = R.find(
459
+ (child) => child.component.props.selected,
460
+ navbar?.children || []
461
+ )?.id;
462
+
463
+ if (selectedMenuItemId) {
464
+ // set focus on selected menu item
465
+ setFocus(selectedMenuItemId);
466
+ return;
467
+ }
468
+
469
+ const firstMenuItemId = R.path(["children", 0, "id"], navbar);
470
+
471
+ if (firstMenuItemId) {
472
+ // set focus on first menu item
473
+ setFocus(firstMenuItemId);
474
+ }
475
+ }
476
+
477
+ function setFocusOnContent() {
478
+ // we need to put focus on content here
479
+ // For doing it we have to wait registration of first focusable node into focusableTree
480
+ // this is the temporary hack with waiting until content node starts having children
481
+ const getContentNode = () => {
482
+ const activeScreen = R.last(focusableTree.root.children);
483
+ const content = R.find(
484
+ (child) => R.startsWith("content", child.id),
485
+ activeScreen.children
486
+ );
487
+
488
+ return content;
489
+ };
490
+
491
+ const contentHasAnyChildren = () => {
492
+ return getContentNode()?.children.length > 0;
493
+ };
494
+
495
+ waitUntil(contentHasAnyChildren)
496
+ .then(() => {
497
+ const content = getContentNode();
498
+
499
+ setInitialFocus(content);
500
+ })
501
+ .catch(() => {
502
+ // we failed to wait any content, put focus on menu as a last chance
503
+ setFocusOnMenu();
504
+ });
505
+ }
506
+
413
507
  /**
414
508
  * moves the focus in a provided direction
415
509
  * @public
@@ -422,6 +516,13 @@ export const focusManager = (function () {
422
516
  currentFocusNode.component.willLoseFocus(currentFocusNode, direction);
423
517
  const nextFocusable = findFocusableNode(direction, currentFocusNode);
424
518
 
519
+ console.log("debug_2", "FM moveFocus", {
520
+ direction: direction?.value,
521
+ currentFocusNode,
522
+ focusableTree,
523
+ nextFocusable,
524
+ });
525
+
425
526
  if (nextFocusable?.component?.isMounted?.()) {
426
527
  const nextRect = nextFocusable.component.getRect();
427
528
 
@@ -518,5 +619,7 @@ export const focusManager = (function () {
518
619
  enableAutomaticFocus,
519
620
  getGroupRootById,
520
621
  isGroupItemFocused,
622
+ setFocusOnMenu,
623
+ setFocusOnContent,
521
624
  };
522
625
  })();
@@ -1,4 +1,4 @@
1
- import * as R from "ramda";
1
+ import { allPass, pathOr, startsWith, isNil } from "ramda";
2
2
  import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
3
3
 
4
4
  import { rejectItemByPath } from "../../../../objectUtils";
@@ -143,7 +143,49 @@ const hasChildren = (target) =>
143
143
 
144
144
  const hasComponent = (target) => !!target?.component;
145
145
 
146
- const hasComponentAndChildren = R.allPass([hasComponent, hasChildren]);
146
+ const hasComponentAndChildren = allPass([hasComponent, hasChildren]);
147
+
148
+ const positiveOrZero = (value) => value >= 0;
149
+
150
+ const excludeFromFocusSearching = pathOr(false, [
151
+ "component",
152
+ "props",
153
+ "excludeFromFocusSearching",
154
+ ]);
155
+
156
+ // const isAllScreens = all(excludeFromFocusSearching);
157
+
158
+ const isNavBar = (target) => startsWith("navbar", target?.id);
159
+
160
+ const ignoreMenuIfMultipleTargets = (nodes) => {
161
+ if (nodes.length > 1 && nodes.map((n) => n?.target?.id).includes("navbar")) {
162
+ return nodes.filter((n) => !isNavBar(n?.target));
163
+ }
164
+
165
+ return nodes;
166
+ };
167
+
168
+ const toDistance = ({ target, direction, comparisonRect }) => {
169
+ const componentRect = target.component?.getRect();
170
+
171
+ const orientationDistance = measureDistance(
172
+ direction,
173
+ comparisonRect,
174
+ componentRect
175
+ );
176
+
177
+ if (orientationDistance === -1) {
178
+ // ignores offscreen targets when moving up/down
179
+ return null;
180
+ }
181
+
182
+ const euclidDistance = measureEuclidianDistanceToCenter(
183
+ comparisonRect,
184
+ componentRect
185
+ );
186
+
187
+ return orientationDistance > 0 ? euclidDistance : orientationDistance;
188
+ };
147
189
 
148
190
  /**
149
191
  * Get target in shortest distance in specified direction
@@ -173,52 +215,32 @@ export function getTargetInDirection(
173
215
 
174
216
  const distances = targets
175
217
  .filter(hasComponentAndChildren)
176
- .map((target) => {
177
- const componentRect = target.component?.getRect();
178
-
179
- const distance = measureDistance(
180
- direction,
181
- comparisonRect,
182
- componentRect
183
- );
184
-
185
- if (distance === -1) {
186
- // ignores offscreen targets when moving up/down
187
- return null;
188
- }
218
+ .filter((target) => !excludeFromFocusSearching(target))
219
+ .map((target) => ({
220
+ distance: toDistance({ target, direction, comparisonRect }),
221
+ target,
222
+ }))
223
+ .filter(({ distance }) => positiveOrZero(distance));
189
224
 
190
- const orientationDistance = measureDistance(
191
- direction,
192
- comparisonRect,
193
- componentRect
194
- );
195
-
196
- const euclidDistance = measureEuclidianDistanceToCenter(
197
- comparisonRect,
198
- componentRect
199
- );
200
-
201
- return {
202
- distance:
203
- orientationDistance > 0 ? euclidDistance : orientationDistance,
204
- target,
205
- };
206
- })
207
- .filter((obj) => obj && obj.distance >= 0);
225
+ // if (isAllScreens(targets)) {
226
+ // return last(targets);
227
+ // }
208
228
 
209
229
  const match = distances
210
230
  .sort((a, b) => a.distance - b.distance) // sorts the distances
211
231
  .filter(ignoreMenuIfMultipleTargets)[0]; // filters out multiple targets if necessary // gets the first element
212
232
 
213
- return distances && distances.length > 0 ? match.target : null;
214
- }
215
-
216
- function ignoreMenuIfMultipleTargets(nodes) {
217
- if (nodes.length > 1 && nodes.map((n) => n?.target?.id).includes("navbar")) {
218
- return nodes.filter((n) => n?.target?.id !== "navbar");
219
- }
220
-
221
- return nodes;
233
+ // console.log("debug_2", "FM getTargetInDirection", {
234
+ // direction: direction?.value,
235
+ // currentNode,
236
+ // targetNodes,
237
+ // targets,
238
+ // comparisonRect,
239
+ // distances,
240
+ // match,
241
+ // });
242
+
243
+ return match?.target;
222
244
  }
223
245
 
224
246
  /**
@@ -309,3 +331,36 @@ export function hasPriorityInDirection(direction, component) {
309
331
 
310
332
  return (prioritiseFocusOn & mappedDirection) > 0;
311
333
  }
334
+
335
+ export const getNodeBeforeRoot = (node) => {
336
+ if (isNil(node)) {
337
+ return undefined;
338
+ }
339
+
340
+ if (node.parent.id === "root") {
341
+ return node;
342
+ }
343
+
344
+ return getNodeBeforeRoot(node.parent);
345
+ };
346
+
347
+ export const isAllowedToMoveTo = (currentNode, nextNode) => {
348
+ const screenForCurrentNode = getNodeBeforeRoot(currentNode);
349
+ const screenForNextNode = getNodeBeforeRoot(nextNode);
350
+
351
+ return screenForCurrentNode.id === screenForNextNode.id;
352
+
353
+ // console.log("debug_2", "isAllowedToMoveTo", {
354
+ // nodeBeforeRoot,
355
+ // });
356
+
357
+ // if (nodeBeforeRoot?.id === "navbar") {
358
+ // return true;
359
+ // }
360
+
361
+ // if (nodeBeforeRoot?.component?.props?.isActive) {
362
+ // return true;
363
+ // }
364
+
365
+ // return false;
366
+ };
@@ -727,4 +727,5 @@ export class Player {
727
727
  }
728
728
 
729
729
  public supportsNativeControls = (): boolean => false;
730
+ public supportNativeCast = (): boolean => false;
730
731
  }
@@ -295,4 +295,9 @@ export class PlayerNative extends Player {
295
295
 
296
296
  supportsNativeControls = (): boolean =>
297
297
  toBooleanWithDefaultFalse(this.getConfig()?.["supports_native_controls"]);
298
+
299
+ supportNativeCast = (): boolean =>
300
+ toBooleanWithDefaultFalse(
301
+ this.playerPluginId === "quick-brick-player-brightcove"
302
+ );
298
303
  }
@@ -245,6 +245,12 @@ const generalContent = () => ({
245
245
  ],
246
246
  initial_value: "reloading...",
247
247
  },
248
+ {
249
+ type: "text_input",
250
+ label: "Navigation Bar Screen Title",
251
+ key: "screen_title",
252
+ initial_value: null,
253
+ },
248
254
  ],
249
255
  },
250
256
  advertising: {
@@ -1663,7 +1663,8 @@ function getPlayerConfiguration({ platform }) {
1663
1663
  platform.includes("lg") ||
1664
1664
  platform.includes("samsung") ||
1665
1665
  platform.includes("android_tv") ||
1666
- platform.includes("amazon")
1666
+ platform.includes("amazon") ||
1667
+ platform.includes("vizio")
1667
1668
  ) {
1668
1669
  styles.fields.push(
1669
1670
  // Captions Menu
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "12.0.0-alpha.4802689107",
3
+ "version": "12.0.0-alpha.4837064762",
4
4
  "description": "Applicaster Zapp React Native utilities package",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "homepage": "https://github.com/applicaster/quickbrick#readme",
30
30
  "dependencies": {
31
- "@applicaster/applicaster-types": "12.0.0-alpha.4802689107",
31
+ "@applicaster/applicaster-types": "12.0.0-alpha.4837064762",
32
32
  "buffer": "^5.2.1",
33
33
  "camelize": "^1.0.0",
34
34
  "create-react-context": "^0.2.3",
@@ -22,6 +22,7 @@ function getDevice(platform) {
22
22
  case "android_tv_for_quickbrick":
23
23
  case "lg_tv":
24
24
  case "samsung_tv":
25
+ case "vizio":
25
26
  case "web":
26
27
  return "tv";
27
28
  }
@@ -1659,7 +1660,8 @@ function getPlayerConfiguration({ platform }) {
1659
1660
  platform.includes("lg") ||
1660
1661
  platform.includes("samsung") ||
1661
1662
  platform.includes("android_tv") ||
1662
- platform.includes("amazon")
1663
+ platform.includes("amazon") ||
1664
+ platform.includes("vizio")
1663
1665
  ) {
1664
1666
  styles.fields.push(
1665
1667
  // Captions Menu for TV
@@ -1,4 +1,8 @@
1
+ import React from "react";
2
+
1
3
  import { act, renderHook } from "@testing-library/react-hooks";
4
+ import { Provider } from "react-redux";
5
+ import configureStore from "redux-mock-store";
2
6
 
3
7
  const mockUpdateComponentsPositions = jest.fn();
4
8
 
@@ -19,11 +23,22 @@ jest.useFakeTimers();
19
23
 
20
24
  jest.mock("@applicaster/zapp-react-native-utils/reactHooks/navigation");
21
25
 
26
+ const mockStore = configureStore();
27
+
28
+ const store = mockStore({ appData: {} });
29
+
30
+ const Wrapper = ({ children }: { children: React.ReactChild }) => (
31
+ /* @ts-ignore */
32
+ <Provider store={store}>{children}</Provider>
33
+ );
34
+
22
35
  const { useTrackedView } = require("../useTrackedView");
23
36
 
24
37
  describe("useTrackCurrentAutoScrollingElement", () => {
25
38
  it("should update position for selected component", async () => {
26
- const { result } = renderHook(() => useTrackedView("123"));
39
+ const { result } = renderHook(() => useTrackedView("123"), {
40
+ wrapper: Wrapper,
41
+ });
27
42
 
28
43
  // Fast-forward until all timers have been executed
29
44
  jest.advanceTimersByTime(3000);
@@ -1,4 +1,8 @@
1
+ import React from "react";
2
+
1
3
  import { cleanup, renderHook } from "@testing-library/react-hooks";
4
+ import { Provider } from "react-redux";
5
+ import configureStore from "redux-mock-store";
2
6
 
3
7
  import * as ReactNative from "react-native";
4
8
 
@@ -48,19 +52,43 @@ jest.mock("../../navigation", () => ({
48
52
  }));
49
53
 
50
54
  const { Dimensions } = require("react-native");
51
- const { useDimensions } = require("../");
55
+ const { useDimensions } = require("..");
56
+
57
+ const mockStore = configureStore();
58
+
59
+ const getWrapper = () => {
60
+ const store = mockStore({ appData: {} });
61
+
62
+ const Wrapper = ({ children }: { children: React.ReactChild }) => (
63
+ /* @ts-ignore */
64
+ <Provider store={store}>{children}</Provider>
65
+ );
66
+
67
+ return Wrapper;
68
+ };
69
+
70
+ const testHook = (context, params?) => {
71
+ const wrapper = getWrapper();
72
+
73
+ const {
74
+ result,
75
+ result: { current: dimensions },
76
+ } = renderHook(() => useDimensions(context, params), {
77
+ wrapper,
78
+ });
79
+
80
+ return { result, dimensions };
81
+ };
52
82
 
53
83
  describe("useDimensions", () => {
54
84
  it("renders", () => {
55
- const { result } = renderHook(() =>
56
- useDimensions("window", { fullDimensions: true })
57
- );
85
+ const { result } = testHook("window", { fullDimensions: true });
58
86
 
59
87
  expect(result).toBeDefined();
60
88
  });
61
89
 
62
90
  it("sets event listener", () => {
63
- renderHook(() => useDimensions("window", { fullDimensions: true }));
91
+ testHook("window", { fullDimensions: true });
64
92
 
65
93
  expect(Dimensions.addEventListener).toBeCalledWith(
66
94
  "change",
@@ -69,16 +97,14 @@ describe("useDimensions", () => {
69
97
  });
70
98
 
71
99
  it("removes event listener", () => {
72
- renderHook(() => useDimensions("window", { fullDimensions: true }));
100
+ testHook("window", { fullDimensions: true });
73
101
  cleanup();
74
102
 
75
103
  expect(mockRemoveFn).toBeCalled();
76
104
  });
77
105
 
78
106
  it("Returns full dimensions object", () => {
79
- const {
80
- result: { current: dimensions },
81
- } = renderHook(() => useDimensions("window", { fullDimensions: true }));
107
+ const { dimensions } = testHook("window", { fullDimensions: true });
82
108
 
83
109
  expect(dimensions).toHaveProperty("height");
84
110
  expect(dimensions).toHaveProperty("width");
@@ -87,20 +113,17 @@ describe("useDimensions", () => {
87
113
  });
88
114
 
89
115
  it("Returns shorthand dimensions object", () => {
90
- const {
91
- result: { current: dimensions },
92
- } = renderHook(() => useDimensions("window"));
116
+ const { dimensions } = testHook("window");
93
117
 
94
118
  expect(dimensions).toHaveProperty("height");
95
119
  expect(dimensions).toHaveProperty("width");
96
120
  });
97
121
 
98
122
  it("Returns the device info object", () => {
99
- const {
100
- result: { current: dimensions },
101
- } = renderHook(() =>
102
- useDimensions("window", { fullDimensions: true, deviceInfo: true })
103
- );
123
+ const { dimensions } = testHook("window", {
124
+ fullDimensions: true,
125
+ deviceInfo: true,
126
+ });
104
127
 
105
128
  expect(dimensions).toHaveProperty("height");
106
129
  expect(dimensions).toHaveProperty("width");
@@ -113,10 +136,7 @@ describe("useDimensions", () => {
113
136
  it("applies the scale on TV, and screen context", () => {
114
137
  tv_flag = true;
115
138
  const { screen } = mocked_return_value(tv_flag);
116
-
117
- const {
118
- result: { current: dimensions },
119
- } = renderHook(() => useDimensions("screen"));
139
+ const { dimensions } = testHook("screen");
120
140
 
121
141
  expect(dimensions).toHaveProperty("height", screen.height * screen.scale);
122
142
  expect(dimensions).toHaveProperty("width", screen.width * screen.scale);
@@ -128,10 +148,7 @@ describe("useDimensions", () => {
128
148
  it("doesn't apply the scale on mobile with screen context", () => {
129
149
  tv_flag = false;
130
150
  const { screen } = mocked_return_value(tv_flag);
131
-
132
- const {
133
- result: { current: dimensions },
134
- } = renderHook(() => useDimensions("screen"));
151
+ const { dimensions } = testHook("screen");
135
152
 
136
153
  expect(dimensions).toHaveProperty("height", screen.height);
137
154
  expect(dimensions).toHaveProperty("width", screen.width);
@@ -141,10 +158,7 @@ describe("useDimensions", () => {
141
158
  it("doesn't apply the scale on TV with window context", () => {
142
159
  tv_flag = true;
143
160
  const { window } = mocked_return_value(tv_flag);
144
-
145
- const {
146
- result: { current: dimensions },
147
- } = renderHook(() => useDimensions("window"));
161
+ const { dimensions } = testHook("window");
148
162
 
149
163
  expect(dimensions).toHaveProperty("height", window.height);
150
164
  expect(dimensions).toHaveProperty("width", window.width);
@@ -3,13 +3,15 @@ import { isTablet } from "../isTablet";
3
3
  /**
4
4
  * Returns an object with additional device info that can be used to determine if tablet
5
5
  * @param {Number} dimensions - Dimensions object passed to the function
6
+ * @param {Object} appData - application data object passed to the function
6
7
  * @returns {DeviceInfo} deviceInfo - returns an object with scale, isTablet, and orientation
7
8
  */
8
- export const getDeviceInfo = (dimensions) => {
9
+ export const getDeviceInfo = (dimensions, appData) => {
9
10
  const scale = dimensions?.scale;
10
11
  const isLandscape = dimensions?.width > dimensions?.height;
11
12
  const orientation = isLandscape ? "landscape" : "portrait";
12
13
  const tablet = isTablet(dimensions, orientation) || false;
14
+ const isTabletLandscape = tablet && !appData?.isTabletPortrait;
13
15
 
14
- return { scale, isTablet: tablet, orientation };
16
+ return { scale, isTablet: tablet, orientation, isTabletLandscape };
15
17
  };
@@ -2,6 +2,7 @@ type DeviceInfo = {
2
2
  isTablet: boolean;
3
3
  scale: number;
4
4
  orientation: string;
5
+ isTabletLandscape: boolean;
5
6
  };
6
7
 
7
8
  type RNDimensions = {
@@ -3,6 +3,8 @@ import { useLayoutEffect, useMemo, useRef, useState } from "react";
3
3
  import * as R from "ramda";
4
4
 
5
5
  import { Dimensions, Platform, StatusBar } from "react-native";
6
+ import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
7
+
6
8
  import { isTV } from "../../../reactUtils";
7
9
 
8
10
  import { Options, UseDimensions } from "../types";
@@ -36,6 +38,7 @@ export const useDimensions: UseDimensions = (
36
38
  fullDimensions = { fullDimensions: false, updateForInactiveScreens: true }
37
39
  ) => {
38
40
  const isActive = useIsScreenActive();
41
+ const { appData } = usePickFromState(["appData"]);
39
42
 
40
43
  if (typeof fullDimensions === "boolean") {
41
44
  // eslint-disable-next-line no-console
@@ -50,7 +53,7 @@ export const useDimensions: UseDimensions = (
50
53
  : { ...fullDimensions };
51
54
 
52
55
  const [dimensions, setDimensions] = useState(getInitialDimensions(context));
53
- const deviceInfo = useRef(getDeviceInfo(dimensions));
56
+ const deviceInfo = useRef(getDeviceInfo(dimensions, appData));
54
57
 
55
58
  const applyOptions = R.compose(
56
59
  R.when(() => options.deviceInfo, R.assoc("deviceInfo", deviceInfo.current)),
@@ -85,7 +88,7 @@ export const useDimensions: UseDimensions = (
85
88
  // eslint-disable-next-line no-console
86
89
  console.log("change: wrong values - width equal to height, ignoring");
87
90
  } else if (isActive || options.updateForInactiveScreens) {
88
- deviceInfo.current = getDeviceInfo(newDimensions);
91
+ deviceInfo.current = getDeviceInfo(newDimensions, appData);
89
92
  setDimensions(newDimensions);
90
93
  }
91
94
  };
@@ -15,8 +15,21 @@ export const mockMenu: ZappNavigation = {
15
15
  // preset_id?: null | unknown;
16
16
  };
17
17
 
18
+ export const useRoute = jest.fn(() => ({
19
+ screenData: {
20
+ name: "someName",
21
+ },
22
+ pathname: "someRoute",
23
+ }));
24
+
18
25
  export const useNavigation = jest.fn(() => ({
19
26
  data: {},
27
+ screenData: {
28
+ name: "someName",
29
+ },
30
+ activeRiver: {
31
+ id: "someId",
32
+ },
20
33
  currentRoute: "someRoute",
21
34
  }));
22
35
 
@@ -1,4 +1,7 @@
1
1
  export const useNavigation = jest.fn(() => ({
2
2
  data: {},
3
+ screenData: {
4
+ name: "someName",
5
+ },
3
6
  currentRoute: "someRoute",
4
7
  }));
@@ -251,6 +251,11 @@ export const useRoute = (): {
251
251
  return composeRouteData(screenData);
252
252
  }
253
253
 
254
+ // case 1: stack not yet updated in navigator context, but navigator.screenData is updated already.
255
+ if (!currentStack) {
256
+ return composeRouteData(navigator.screenData);
257
+ }
258
+
254
259
  return composeRouteData(currentRouteScreenData);
255
260
  };
256
261
 
@@ -491,3 +496,18 @@ export const useIsScreenActive = () => {
491
496
 
492
497
  return pathname === currentRoute;
493
498
  };
499
+
500
+ type Return = (prefix: string) => string;
501
+
502
+ export const useUniqueRouteSuffix = (): Return => {
503
+ const pathname = usePathname();
504
+ const { currentRoute } = useNavigation();
505
+
506
+ const suffix = `currentRoute:${currentRoute}_pathname:${pathname}`;
507
+
508
+ const withUniqueRouteSuffix = React.useCallback((prefix: string) => {
509
+ return `${prefix}___${suffix}`;
510
+ }, []);
511
+
512
+ return withUniqueRouteSuffix;
513
+ };
@@ -5,8 +5,9 @@ import { useNestedNavigationContext } from "@applicaster/zapp-react-native-ui-co
5
5
  import { useNavigation } from "../navigation/useNavigation";
6
6
 
7
7
  import { ScreenContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenContext";
8
+ import { ScreenDataContext } from "@applicaster/zapp-react-native-ui-components/Components/Layout/TV";
8
9
 
9
- function useScreenContextV2() {
10
+ export function useScreenContextV2() {
10
11
  return useContext(ScreenContext);
11
12
  }
12
13
 
@@ -45,8 +46,8 @@ export function useSetNavbarState() {
45
46
  export function useScreenContext(): NavigationScreenData {
46
47
  const navigator = useNavigation();
47
48
 
48
- const screenKey = useMemo(() => navigator?.key, [navigator]);
49
- const screenData = useMemo(() => navigator?.data, [screenKey]);
49
+ const screenData = useContext(ScreenDataContext);
50
+
50
51
  const modalScreenData = navigator?.modalData;
51
52
 
52
53
  const isModal = useModalNavigationContext();
@@ -7,7 +7,7 @@ export const isAnyNil = R.any(R.isNil);
7
7
 
8
8
  export const isNilOrEmpty = R.either(R.isNil, R.isEmpty);
9
9
 
10
- export const isNotEmpty = R.complement(R.isEmpty);
10
+ export const isNotEmpty = R.both(isNotNil, R.complement(R.isEmpty));
11
11
 
12
12
  export const withoutNil = R.reject(R.isNil);
13
13
 
@@ -49,11 +49,3 @@ export function useScreenState(
49
49
  setSelectedEntry: (entryId) => setSelectedEntry(screenId, entryId),
50
50
  };
51
51
  }
52
-
53
- export const useScreenScrollStore = create<{
54
- scrollPosition: number;
55
- scrollingVelocity: number;
56
- }>(() => ({
57
- scrollPosition: 0,
58
- scrollingVelocity: 0,
59
- }));
@@ -0,0 +1,14 @@
1
+ export const getByTestId = (wrapper, testId) => {
2
+ try {
3
+ if (wrapper?.getByTestId) {
4
+ return wrapper?.queryAllByTestId(testId)?.[0] ?? undefined;
5
+ } else if (wrapper?.root?.findByProps) {
6
+ return wrapper.root.findAllByProps({ testID: testId })?.[0] ?? undefined;
7
+ }
8
+ } catch (e) {
9
+ // eslint-disable-next-line no-console
10
+ console.log("getByTestId E:", e);
11
+
12
+ return undefined;
13
+ }
14
+ };
@@ -1,6 +1,6 @@
1
1
  import * as R from "ramda";
2
2
 
3
- import React from "react";
3
+ import React, { PropsWithChildren } from "react";
4
4
  import { View } from "react-native";
5
5
 
6
6
  import { Provider } from "react-redux";
@@ -11,6 +11,8 @@ import { SafeAreaProvider } from "react-native-safe-area-context";
11
11
  import { render } from "@testing-library/react-native";
12
12
  import { AnalyticsProvider } from "../analyticsUtils";
13
13
 
14
+ export { getByTestId } from "./getByTestId";
15
+
14
16
  /**
15
17
  * Function to be used in tandem with enzyme findWhere(node => node.prop('testID') === 'yourID')
16
18
  * Example:
@@ -21,17 +23,6 @@ import { AnalyticsProvider } from "../analyticsUtils";
21
23
  export const testIDEquals = (testId: any): boolean =>
22
24
  R.compose(R.equals(testId), R.invoker(1, "prop")("testID"));
23
25
 
24
- export const getByTestId = (wrapper, testId) => {
25
- try {
26
- return wrapper?.queryAllByTestId(testId)?.[0] ?? undefined;
27
- } catch (e) {
28
- // eslint-disable-next-line no-console
29
- console.log("getByTestId E:", e);
30
-
31
- return undefined;
32
- }
33
- };
34
-
35
26
  /** Helper function for react-test-renderer, accepts type and renderer
36
27
  * * @param {*} type React Component type instance or name of component
37
28
  * * @param {*} renderer renderer instance
@@ -47,11 +38,11 @@ export const findByType = (type, renderer) => {
47
38
  export function createComponentMock(mockName, hasChildren = false) {
48
39
  return hasChildren
49
40
  ? jest.fn(({ children }) => (
50
- <View comp={mockName} testID={mockName}>
41
+ <View testID={mockName}>
51
42
  {typeof children === "function" ? children() : children}
52
43
  </View>
53
44
  ))
54
- : jest.fn(() => <View comp={mockName} testID={mockName} />);
45
+ : jest.fn(() => <View testID={mockName} />);
55
46
  }
56
47
 
57
48
  const initialState = {
@@ -64,14 +55,21 @@ const initialState = {
64
55
  AppContainer: createComponentMock("appContainer", true),
65
56
  DisplayError: createComponentMock("displayError"),
66
57
  },
67
- rivers: [{ id: "A1234", home: true }],
58
+ rivers: { id: "A1234", home: true },
68
59
  appSettings: {},
69
60
  plugins: [],
70
- appState: { appReady: true },
61
+ appState: { appReady: true, appLaunched: true },
62
+ appData: { layoutVersion: "v1" },
63
+ contentTypes: null,
71
64
  };
72
65
 
73
- export const WrappedWithProviders = ({ children, store = {} }: any) => {
74
- const _store = configureStore([thunk])({ ...initialState, ...store });
66
+ export const WrappedWithProviders = ({
67
+ children,
68
+ store: storeObj = {},
69
+ }: any) => {
70
+ const _store = configureStore([thunk])(
71
+ R.mergeDeepWith(R.concat, { ...initialState }, storeObj)
72
+ );
75
73
 
76
74
  return (
77
75
  <SafeAreaProvider
@@ -93,8 +91,12 @@ export const WrappedWithProviders = ({ children, store = {} }: any) => {
93
91
  * @param store optional store to pass to the provider
94
92
  * @returns
95
93
  */
96
- export const renderWithProviders = (component, store?: unknown) => {
97
- return render(
98
- <WrappedWithProviders store={store}>{component}</WrappedWithProviders>
99
- );
94
+ export const renderWithProviders = (component, storeObj?: unknown) => {
95
+ return render(component, {
96
+ wrapper: function TestWrapper({ children }: PropsWithChildren<any>) {
97
+ return (
98
+ <WrappedWithProviders store={storeObj}>{children}</WrappedWithProviders>
99
+ );
100
+ },
101
+ });
100
102
  };