@applicaster/zapp-react-native-utils 14.0.0-alpha.5114565431 → 14.0.0-alpha.5243406255
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.
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +4 -0
- package/appUtils/focusManager/__tests__/focusManager.test.js +1 -1
- package/appUtils/focusManager/index.ts +81 -9
- package/appUtils/focusManagerAux/utils/index.ts +48 -2
- package/navigationUtils/__tests__/mapContentTypesToRivers.test.ts +130 -0
- package/navigationUtils/index.ts +6 -4
- package/package.json +2 -2
- package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +3 -1
- package/reactHooks/feed/useBatchLoading.ts +8 -6
- package/reactHooks/feed/useFeedLoader.tsx +12 -8
- package/reactHooks/feed/usePipesCacheReset.ts +2 -2
- package/reactHooks/layout/__tests__/index.test.tsx +3 -1
- package/reactHooks/layout/useDimensions/__tests__/useDimensions.test.ts +34 -36
- package/reactHooks/layout/useDimensions/useDimensions.ts +2 -3
- package/reactHooks/layout/useLayoutVersion.ts +5 -5
- package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +4 -0
- package/reactHooks/state/useRivers.ts +7 -8
- package/utils/index.ts +1 -0
|
@@ -6,6 +6,7 @@ exports[`focusManager should be defined 1`] = `
|
|
|
6
6
|
"disableFocus": [Function],
|
|
7
7
|
"enableFocus": [Function],
|
|
8
8
|
"findPreferredFocusChild": [Function],
|
|
9
|
+
"focusTopNavigation": [Function],
|
|
9
10
|
"focusableTree": Tree {
|
|
10
11
|
"loadingCounter": 0,
|
|
11
12
|
"root": {
|
|
@@ -24,7 +25,10 @@ exports[`focusManager should be defined 1`] = `
|
|
|
24
25
|
"invokeHandler": [Function],
|
|
25
26
|
"isCurrentFocusOnTheTopScreen": [Function],
|
|
26
27
|
"isFocusDisabled": [Function],
|
|
28
|
+
"isFocusOnContent": [Function],
|
|
29
|
+
"isFocusOnMenu": [Function],
|
|
27
30
|
"isGroupItemFocused": [Function],
|
|
31
|
+
"isOnRootScreen": [Function],
|
|
28
32
|
"longPress": [Function],
|
|
29
33
|
"moveFocus": [Function],
|
|
30
34
|
"on": [Function],
|
|
@@ -14,6 +14,15 @@ import { subscriber } from "../../functionUtils";
|
|
|
14
14
|
import { coreLogger } from "../../logger";
|
|
15
15
|
import { ACTION } from "./utils/enums";
|
|
16
16
|
|
|
17
|
+
import {
|
|
18
|
+
isTabsScreen,
|
|
19
|
+
findSelectedTabId,
|
|
20
|
+
findSelectedMenuId,
|
|
21
|
+
isTabsMenuFocused,
|
|
22
|
+
isCurrentFocusOnContent,
|
|
23
|
+
isCurrentFocusOnMenu,
|
|
24
|
+
} from "../focusManagerAux/utils";
|
|
25
|
+
|
|
17
26
|
const logger = coreLogger.addSubsystem("focusManager");
|
|
18
27
|
|
|
19
28
|
const isFocusEnabled = (focusableItem): boolean => {
|
|
@@ -100,7 +109,7 @@ export const focusManager = (function () {
|
|
|
100
109
|
* @private
|
|
101
110
|
* @param {Object} direction of the navigation which led to this action
|
|
102
111
|
*/
|
|
103
|
-
function focus(direction) {
|
|
112
|
+
function focus(direction, context?: FocusManager.FocusContext) {
|
|
104
113
|
const currentFocusable = getCurrentFocus();
|
|
105
114
|
|
|
106
115
|
if (
|
|
@@ -108,7 +117,7 @@ export const focusManager = (function () {
|
|
|
108
117
|
!currentFocusable.isGroup &&
|
|
109
118
|
currentFocusable.isMounted()
|
|
110
119
|
) {
|
|
111
|
-
currentFocusable.setFocus(direction);
|
|
120
|
+
currentFocusable.setFocus(direction, context);
|
|
112
121
|
}
|
|
113
122
|
}
|
|
114
123
|
|
|
@@ -205,7 +214,7 @@ export const focusManager = (function () {
|
|
|
205
214
|
* @param {Array<string>} ids - An array of node IDs to update.
|
|
206
215
|
* @param {boolean} setFocus - A flag indicating whether to set focus (true) or blur (false) on the nodes.
|
|
207
216
|
*/
|
|
208
|
-
const updateNodeFocus = (ids, action) => {
|
|
217
|
+
const updateNodeFocus = (ids, action, context: FocusManager.FocusContext) => {
|
|
209
218
|
if (!ids || ids.length === 0) {
|
|
210
219
|
return; // Nothing to do
|
|
211
220
|
}
|
|
@@ -222,11 +231,13 @@ export const focusManager = (function () {
|
|
|
222
231
|
|
|
223
232
|
// Function to apply the action (focus or blur)
|
|
224
233
|
const applyAction = (node) => {
|
|
234
|
+
const direction = undefined;
|
|
235
|
+
|
|
225
236
|
if (node && node.component) {
|
|
226
237
|
if (action === "focus") {
|
|
227
|
-
node.component.setFocus();
|
|
238
|
+
node.component.setFocus(direction, context);
|
|
228
239
|
} else if (action === "blur") {
|
|
229
|
-
node.component.setBlur();
|
|
240
|
+
node.component.setBlur(direction, context);
|
|
230
241
|
}
|
|
231
242
|
}
|
|
232
243
|
};
|
|
@@ -253,7 +264,11 @@ export const focusManager = (function () {
|
|
|
253
264
|
* @param {Object} direction of the navigation, which led to this focus change
|
|
254
265
|
* to another group or not. defaults to false
|
|
255
266
|
*/
|
|
256
|
-
function setFocus(
|
|
267
|
+
function setFocus(
|
|
268
|
+
id: string,
|
|
269
|
+
direction?: FocusManager.Web.Direction,
|
|
270
|
+
context?: FocusManager.FocusContext
|
|
271
|
+
) {
|
|
257
272
|
if (focusDisabled) return false;
|
|
258
273
|
|
|
259
274
|
// due to optimisiation it's recommanded to set currentFocusNode before setFocus
|
|
@@ -266,15 +281,61 @@ export const focusManager = (function () {
|
|
|
266
281
|
);
|
|
267
282
|
|
|
268
283
|
// Set focus on current node parents and blur on previous node parents
|
|
269
|
-
updateNodeFocus(currentNodeParentsIDs, ACTION.FOCUS);
|
|
270
|
-
updateNodeFocus(previousNodeParentsIDs, ACTION.BLUR);
|
|
284
|
+
updateNodeFocus(currentNodeParentsIDs, ACTION.FOCUS, context);
|
|
285
|
+
updateNodeFocus(previousNodeParentsIDs, ACTION.BLUR, context);
|
|
271
286
|
|
|
272
287
|
currentFocusNode = focusableTree.findInTree(id);
|
|
273
288
|
}
|
|
274
289
|
|
|
275
290
|
setLastFocusOnParentNode(currentFocusNode);
|
|
276
291
|
|
|
277
|
-
focus(direction);
|
|
292
|
+
focus(direction, context);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function isFocusOnContent() {
|
|
296
|
+
return isCurrentFocusOnContent(currentFocusNode);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function isFocusOnMenu() {
|
|
300
|
+
return isCurrentFocusOnMenu(currentFocusNode);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function landFocusTo(id) {
|
|
304
|
+
if (id) {
|
|
305
|
+
// set focus on selected menu item
|
|
306
|
+
const direction = undefined;
|
|
307
|
+
|
|
308
|
+
const context: FocusManager.FocusContext = {
|
|
309
|
+
source: "back",
|
|
310
|
+
preserveScroll: true,
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
blur(direction);
|
|
314
|
+
setFocus(id, direction, context);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Move focus to appropriate top navigation tab with context
|
|
319
|
+
function focusTopNavigation() {
|
|
320
|
+
// Store current focus for restoration
|
|
321
|
+
// this.storeFocusState();
|
|
322
|
+
|
|
323
|
+
if (isTabsScreen(focusableTree) && !isTabsMenuFocused(currentFocusNode)) {
|
|
324
|
+
const selectedTabId = findSelectedTabId(focusableTree);
|
|
325
|
+
|
|
326
|
+
console.log("debug_2", "FM - moveFocusToSelectedTab", { selectedTabId });
|
|
327
|
+
|
|
328
|
+
landFocusTo(selectedTabId);
|
|
329
|
+
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Set focus with back button context
|
|
334
|
+
const selectedMenuItemId = findSelectedMenuId(focusableTree);
|
|
335
|
+
|
|
336
|
+
console.log("debug_2", "IM - moveFocusToTopMenu", { selectedMenuItemId });
|
|
337
|
+
|
|
338
|
+
landFocusTo(selectedMenuItemId);
|
|
278
339
|
}
|
|
279
340
|
|
|
280
341
|
/**
|
|
@@ -483,6 +544,12 @@ export const focusManager = (function () {
|
|
|
483
544
|
return haveSameParentBeforeRoot(currentFocusNode, R.last(routes));
|
|
484
545
|
}
|
|
485
546
|
|
|
547
|
+
function isOnRootScreen() {
|
|
548
|
+
const routes = R.pathOr([], ["root", "children"], focusableTree);
|
|
549
|
+
|
|
550
|
+
return routes.length <= 1;
|
|
551
|
+
}
|
|
552
|
+
|
|
486
553
|
function recoverFocus() {
|
|
487
554
|
if (!isCurrentFocusOnTheTopScreen()) {
|
|
488
555
|
// We've failed to set focused node on the new screen => run focus recovery
|
|
@@ -576,5 +643,10 @@ export const focusManager = (function () {
|
|
|
576
643
|
recoverFocus,
|
|
577
644
|
isCurrentFocusOnTheTopScreen,
|
|
578
645
|
findPreferredFocusChild,
|
|
646
|
+
|
|
647
|
+
focusTopNavigation,
|
|
648
|
+
isFocusOnContent,
|
|
649
|
+
isFocusOnMenu,
|
|
650
|
+
isOnRootScreen,
|
|
579
651
|
};
|
|
580
652
|
})();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isNil } from "@applicaster/zapp-react-native-utils/utils";
|
|
2
2
|
import { find, last, pathOr, startsWith } from "ramda";
|
|
3
3
|
import {
|
|
4
4
|
QUICK_BRICK_CONTENT,
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
// run this check too often could lead to performance penalty on low-end devices
|
|
10
10
|
const HOW_OFTEN_TO_CHECK_CONDITION = 300; // ms
|
|
11
11
|
|
|
12
|
+
const TABS_GROUP_ID = "PickerSelector.sp-river";
|
|
13
|
+
|
|
12
14
|
type Props = {
|
|
13
15
|
maxTimeout: number;
|
|
14
16
|
conditionFn: () => boolean;
|
|
@@ -49,7 +51,7 @@ export const waitForActiveScreen = (currentRoute: string, focusableTree) => {
|
|
|
49
51
|
|
|
50
52
|
const route = find((route) => route.id === currentRoute, routes);
|
|
51
53
|
|
|
52
|
-
return
|
|
54
|
+
return !isNil(route);
|
|
53
55
|
};
|
|
54
56
|
|
|
55
57
|
return waitUntil({
|
|
@@ -99,3 +101,47 @@ export const waitForContent = (focusableTree) => {
|
|
|
99
101
|
conditionFn: contentHasAnyChildren,
|
|
100
102
|
});
|
|
101
103
|
};
|
|
104
|
+
|
|
105
|
+
export function isTabsScreen(focusableTree) {
|
|
106
|
+
const tabsGroup = focusableTree.findInTree(TABS_GROUP_ID);
|
|
107
|
+
|
|
108
|
+
return !!tabsGroup;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export const findSelectedTabId = (focusableTree) => {
|
|
112
|
+
// FIXME - find elegant way how to get ID of selected tab
|
|
113
|
+
const tabsGroup = focusableTree.findInTree(TABS_GROUP_ID);
|
|
114
|
+
|
|
115
|
+
const selectedTabId = tabsGroup.children.find(
|
|
116
|
+
(child) => child.component.props.preferredFocus // ?? selected
|
|
117
|
+
)?.id;
|
|
118
|
+
|
|
119
|
+
return selectedTabId;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const findSelectedMenuId = (focusableTree) => {
|
|
123
|
+
// Set focus with back button context
|
|
124
|
+
const navbar = getNavbarNode(focusableTree);
|
|
125
|
+
|
|
126
|
+
const selectedMenuItemId = find(
|
|
127
|
+
(child) => child.component.props.selected,
|
|
128
|
+
navbar.children
|
|
129
|
+
)?.id;
|
|
130
|
+
|
|
131
|
+
return selectedMenuItemId;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export const isTabsMenuFocused = (node) => {
|
|
135
|
+
// FIXME - find elegant way how to get ID of selected tab
|
|
136
|
+
return node.parent.id === TABS_GROUP_ID;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export const isCurrentFocusOnMenu = (node) => {
|
|
140
|
+
// FIXME
|
|
141
|
+
return node.parent.id.startsWith(QUICK_BRICK_NAVBAR);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export const isCurrentFocusOnContent = (node) => {
|
|
145
|
+
// FIXME
|
|
146
|
+
return !isCurrentFocusOnMenu(node);
|
|
147
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { mapContentTypesToRivers } from "../index";
|
|
2
|
+
|
|
3
|
+
describe("mapContentTypesToRivers", () => {
|
|
4
|
+
it("should return the correct content types mapped to rivers", () => {
|
|
5
|
+
const state = {
|
|
6
|
+
rivers: {
|
|
7
|
+
"river-1": {
|
|
8
|
+
plugin_type: "river",
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
contentTypes: {
|
|
12
|
+
"content-type-1": {
|
|
13
|
+
screen_id: "river-1",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const result = mapContentTypesToRivers(state);
|
|
19
|
+
|
|
20
|
+
expect(result).toEqual({
|
|
21
|
+
"content-type-1": {
|
|
22
|
+
screenType: "river",
|
|
23
|
+
screen_id: "river-1",
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should return null if contentTypes is undefined", () => {
|
|
29
|
+
const state = {
|
|
30
|
+
rivers: {
|
|
31
|
+
"river-1": {
|
|
32
|
+
plugin_type: "river",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
// contentTypes is missing
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const result = mapContentTypesToRivers(state);
|
|
39
|
+
|
|
40
|
+
expect(result).toBeNull();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should skip content types whose screen does not exist in rivers", () => {
|
|
44
|
+
const state = {
|
|
45
|
+
rivers: {
|
|
46
|
+
"river-1": {
|
|
47
|
+
plugin_type: "river",
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
contentTypes: {
|
|
51
|
+
"content-type-1": {
|
|
52
|
+
screen_id: "river-1",
|
|
53
|
+
},
|
|
54
|
+
"content-type-2": {
|
|
55
|
+
screen_id: "river-2", // river-2 does not exist
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const result = mapContentTypesToRivers(state);
|
|
61
|
+
|
|
62
|
+
expect(result).toEqual({
|
|
63
|
+
"content-type-1": {
|
|
64
|
+
screenType: "river",
|
|
65
|
+
screen_id: "river-1",
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// result is not null, but may be undefined for missing keys
|
|
70
|
+
expect(result && result["content-type-2"]).toBeUndefined();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should use 'type' if 'plugin_type' is not present in river", () => {
|
|
74
|
+
const state = {
|
|
75
|
+
rivers: {
|
|
76
|
+
"river-1": {
|
|
77
|
+
type: "custom-type",
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
contentTypes: {
|
|
81
|
+
"content-type-1": {
|
|
82
|
+
screen_id: "river-1",
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const result = mapContentTypesToRivers(state);
|
|
88
|
+
|
|
89
|
+
expect(result).toEqual({
|
|
90
|
+
"content-type-1": {
|
|
91
|
+
screenType: "custom-type",
|
|
92
|
+
screen_id: "river-1",
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("should skip content types if neither plugin_type nor type is present in river", () => {
|
|
98
|
+
const state = {
|
|
99
|
+
rivers: {
|
|
100
|
+
"river-1": {
|
|
101
|
+
// no plugin_type or type
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
contentTypes: {
|
|
105
|
+
"content-type-1": {
|
|
106
|
+
screen_id: "river-1",
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const result = mapContentTypesToRivers(state);
|
|
112
|
+
|
|
113
|
+
expect(result).toEqual({});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("should handle empty contentTypes object", () => {
|
|
117
|
+
const state = {
|
|
118
|
+
rivers: {
|
|
119
|
+
"river-1": {
|
|
120
|
+
plugin_type: "river",
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
contentTypes: {},
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const result = mapContentTypesToRivers(state);
|
|
127
|
+
|
|
128
|
+
expect(result).toEqual({});
|
|
129
|
+
});
|
|
130
|
+
});
|
package/navigationUtils/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
isPlayable,
|
|
14
14
|
isV2River,
|
|
15
15
|
} from "./itemTypeMatchers";
|
|
16
|
+
import { RootState } from "@applicaster/zapp-react-native-redux/store";
|
|
16
17
|
|
|
17
18
|
type PathAttribute = {
|
|
18
19
|
screenType: string;
|
|
@@ -377,10 +378,11 @@ export const usesVideoModal = (
|
|
|
377
378
|
return targetScreenConfiguration?.styles?.use_video_modal;
|
|
378
379
|
};
|
|
379
380
|
|
|
380
|
-
export const mapContentTypesToRivers = (
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
381
|
+
export const mapContentTypesToRivers = (
|
|
382
|
+
state: Partial<RootState>
|
|
383
|
+
): ZappContentTypesMapped | null => {
|
|
384
|
+
const { rivers, contentTypes } = state;
|
|
385
|
+
|
|
384
386
|
if (!contentTypes) {
|
|
385
387
|
return null;
|
|
386
388
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-utils",
|
|
3
|
-
"version": "14.0.0-alpha.
|
|
3
|
+
"version": "14.0.0-alpha.5243406255",
|
|
4
4
|
"description": "Applicaster Zapp React Native utilities package",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@applicaster/applicaster-types": "14.0.0-alpha.
|
|
30
|
+
"@applicaster/applicaster-types": "14.0.0-alpha.5243406255",
|
|
31
31
|
"buffer": "^5.2.1",
|
|
32
32
|
"camelize": "^1.0.0",
|
|
33
33
|
"dayjs": "^1.11.10",
|
|
@@ -23,7 +23,9 @@ jest.mock(
|
|
|
23
23
|
|
|
24
24
|
jest.useFakeTimers();
|
|
25
25
|
|
|
26
|
-
jest.mock(
|
|
26
|
+
jest.mock(
|
|
27
|
+
"@applicaster/zapp-react-native-utils/reactHooks/navigation/useNavigation"
|
|
28
|
+
);
|
|
27
29
|
|
|
28
30
|
const mockStore = configureStore();
|
|
29
31
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { complement, compose, isNil, map, min, prop, take, uniq } from "ramda";
|
|
2
|
-
import { useDispatch } from "react-redux";
|
|
3
2
|
import * as React from "react";
|
|
4
|
-
import {
|
|
5
|
-
|
|
3
|
+
import {
|
|
4
|
+
ZappPipes,
|
|
5
|
+
useAppDispatch,
|
|
6
|
+
useZappPipesFeed,
|
|
7
|
+
} from "@applicaster/zapp-react-native-redux";
|
|
6
8
|
import { isNilOrEmpty } from "../../reactUtils/helpers";
|
|
7
9
|
import { ZappPipesSearchContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
|
|
8
10
|
import {
|
|
@@ -63,7 +65,7 @@ export const useBatchLoading = (
|
|
|
63
65
|
componentsToRender: { data?: ZappDataSource; component_type: string }[],
|
|
64
66
|
options: Options
|
|
65
67
|
) => {
|
|
66
|
-
const dispatch =
|
|
68
|
+
const dispatch = useAppDispatch();
|
|
67
69
|
const { screen: screenContext, entry: entryContext } = useScreenContext();
|
|
68
70
|
const [searchContext] = ZappPipesSearchContext.useZappPipesContext();
|
|
69
71
|
const [hasEverBeenReady, setHasEverBeenReady] = React.useState(false);
|
|
@@ -118,7 +120,7 @@ export const useBatchLoading = (
|
|
|
118
120
|
[]
|
|
119
121
|
);
|
|
120
122
|
|
|
121
|
-
const feeds =
|
|
123
|
+
const feeds = useZappPipesFeed(feedUrls);
|
|
122
124
|
|
|
123
125
|
// dispatch loadPipesData for each feed that is not loaded
|
|
124
126
|
const runBatchLoading = React.useCallback(() => {
|
|
@@ -139,7 +141,7 @@ export const useBatchLoading = (
|
|
|
139
141
|
if (mappedFeedUrl) {
|
|
140
142
|
// 4. load data
|
|
141
143
|
return dispatch(
|
|
142
|
-
loadPipesData(mappedFeedUrl, { riverId: options.riverId })
|
|
144
|
+
ZappPipes.loadPipesData(mappedFeedUrl, { riverId: options.riverId })
|
|
143
145
|
);
|
|
144
146
|
}
|
|
145
147
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React, { useEffect } from "react";
|
|
2
|
-
import { useDispatch } from "react-redux";
|
|
3
2
|
|
|
4
|
-
import {
|
|
5
|
-
|
|
3
|
+
import {
|
|
4
|
+
ZappPipes,
|
|
5
|
+
useAppDispatch,
|
|
6
|
+
useZappPipesFeed,
|
|
7
|
+
} from "@applicaster/zapp-react-native-redux";
|
|
6
8
|
|
|
7
9
|
import { reactHooksLogger } from "../logger";
|
|
8
10
|
import { shouldDispatchData, useIsInitialRender } from "../utils";
|
|
@@ -49,7 +51,7 @@ export const useFeedLoader = ({
|
|
|
49
51
|
}, []);
|
|
50
52
|
|
|
51
53
|
const isInitialRender = useIsInitialRender();
|
|
52
|
-
const dispatch =
|
|
54
|
+
const dispatch = useAppDispatch();
|
|
53
55
|
const { screenData } = useRoute();
|
|
54
56
|
|
|
55
57
|
const callableFeedUrl = useInflatedUrl({ feedUrl, mapping });
|
|
@@ -64,7 +66,7 @@ export const useFeedLoader = ({
|
|
|
64
66
|
(silentRefresh = true, callback) => {
|
|
65
67
|
if (callableFeedUrl) {
|
|
66
68
|
dispatch(
|
|
67
|
-
loadPipesData(callableFeedUrl, {
|
|
69
|
+
ZappPipes.loadPipesData(callableFeedUrl, {
|
|
68
70
|
clearCache: true,
|
|
69
71
|
silentRefresh,
|
|
70
72
|
callback,
|
|
@@ -82,7 +84,7 @@ export const useFeedLoader = ({
|
|
|
82
84
|
|
|
83
85
|
if (nextFeed) {
|
|
84
86
|
dispatch(
|
|
85
|
-
loadPipesData(nextFeed, {
|
|
87
|
+
ZappPipes.loadPipesData(nextFeed, {
|
|
86
88
|
silentRefresh: true,
|
|
87
89
|
parentFeed: callableFeedUrl,
|
|
88
90
|
riverId,
|
|
@@ -98,7 +100,7 @@ export const useFeedLoader = ({
|
|
|
98
100
|
) {
|
|
99
101
|
if (callableFeedUrl && !pipesOptions.skipLoading) {
|
|
100
102
|
dispatch(
|
|
101
|
-
loadPipesData(callableFeedUrl, {
|
|
103
|
+
ZappPipes.loadPipesData(callableFeedUrl, {
|
|
102
104
|
...pipesOptions,
|
|
103
105
|
clearCache: true,
|
|
104
106
|
riverId,
|
|
@@ -131,7 +133,9 @@ export const useFeedLoader = ({
|
|
|
131
133
|
// Reload feed when feedUrl changes, unless skipLoading is true
|
|
132
134
|
useEffect(() => {
|
|
133
135
|
if (!isInitialRender && callableFeedUrl && !pipesOptions.skipLoading) {
|
|
134
|
-
dispatch(
|
|
136
|
+
dispatch(
|
|
137
|
+
ZappPipes.loadPipesData(callableFeedUrl, { ...pipesOptions, riverId })
|
|
138
|
+
);
|
|
135
139
|
}
|
|
136
140
|
}, [callableFeedUrl]);
|
|
137
141
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { useDispatch } from "react-redux";
|
|
3
2
|
|
|
4
3
|
import { getDatasourceUrl } from "@applicaster/zapp-react-native-ui-components/Decorators/RiverFeedLoader/utils/getDatasourceUrl";
|
|
5
4
|
import { usePipesContexts } from "@applicaster/zapp-react-native-ui-components/Decorators/RiverFeedLoader/utils/usePipesContexts";
|
|
6
5
|
import { clearPipesData } from "@applicaster/zapp-react-native-redux/ZappPipes";
|
|
7
6
|
|
|
8
7
|
import { useRoute } from "../navigation";
|
|
8
|
+
import { useAppDispatch } from "@applicaster/zapp-react-native-redux";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* reset river components cache when screen is unmounted
|
|
@@ -13,7 +13,7 @@ import { useRoute } from "../navigation";
|
|
|
13
13
|
* @param {Array} riverComponents list of UI components
|
|
14
14
|
*/
|
|
15
15
|
export const usePipesCacheReset = (riverId, riverComponents) => {
|
|
16
|
-
const dispatch =
|
|
16
|
+
const dispatch = useAppDispatch();
|
|
17
17
|
const { screenData, pathname } = useRoute();
|
|
18
18
|
const pipesContexts = usePipesContexts(riverId, pathname);
|
|
19
19
|
|
|
@@ -42,15 +42,17 @@ jest.mock("react-native-safe-area-context", () => ({
|
|
|
42
42
|
}));
|
|
43
43
|
|
|
44
44
|
jest.mock("../../../reactUtils", () => ({
|
|
45
|
+
...jest.requireActual("../../../reactUtils"),
|
|
45
46
|
platformSelect: jest.fn((specs) => specs[platform] || specs.default),
|
|
46
47
|
isTV: jest.fn(() => mock_tv_flag),
|
|
47
48
|
}));
|
|
48
49
|
|
|
49
50
|
jest.mock("../../navigation", () => ({
|
|
50
|
-
useNavigation: () => null,
|
|
51
51
|
useIsScreenActive: () => true,
|
|
52
52
|
}));
|
|
53
53
|
|
|
54
|
+
jest.mock("../../navigation/useNavigation");
|
|
55
|
+
|
|
54
56
|
const { Dimensions } = require("react-native");
|
|
55
57
|
const { useDimensions } = require("..");
|
|
56
58
|
|
|
@@ -1,46 +1,48 @@
|
|
|
1
1
|
import { renderHook } from "@testing-library/react-hooks";
|
|
2
2
|
import { Dimensions, StatusBar } from "react-native";
|
|
3
|
+
import { useDimensions } from "../useDimensions";
|
|
4
|
+
import { usePickFromState } from "@applicaster/zapp-react-native-redux";
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
const mockUseIsScreenActive = jest.fn();
|
|
6
|
-
const mockGetInitialDimensions = jest.fn();
|
|
7
|
-
const mockGetDeviceInfo = jest.fn();
|
|
6
|
+
import { useIsScreenActive } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useIsScreenActive";
|
|
8
7
|
|
|
9
|
-
jest.mock("@applicaster/zapp-react-native-redux/hooks", () =>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
useIsScreenActive: mockUseIsScreenActive,
|
|
16
|
-
}));
|
|
8
|
+
jest.mock("@applicaster/zapp-react-native-redux/hooks", () => {
|
|
9
|
+
return {
|
|
10
|
+
...jest.requireActual("@applicaster/zapp-react-native-redux/hooks"),
|
|
11
|
+
usePickFromState: jest.fn(),
|
|
12
|
+
};
|
|
13
|
+
});
|
|
17
14
|
|
|
18
|
-
jest.mock(
|
|
19
|
-
|
|
15
|
+
jest.mock(
|
|
16
|
+
"@applicaster/zapp-react-native-utils/reactHooks/navigation/useIsScreenActive",
|
|
17
|
+
() => ({
|
|
18
|
+
useIsScreenActive: jest.fn().mockReturnValue(true),
|
|
19
|
+
})
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
jest.doMock("../helpers", () => ({
|
|
23
|
+
getInitialDimensions: jest
|
|
24
|
+
.fn()
|
|
25
|
+
.mockReturnValue({ width: 100, height: 200, scale: 1, fontScale: 1 }),
|
|
20
26
|
}));
|
|
21
27
|
|
|
22
28
|
jest.mock("../../getDeviceInfo", () => ({
|
|
23
|
-
getDeviceInfo:
|
|
29
|
+
getDeviceInfo: jest.fn().mockReturnValue({ deviceInfo: "testDeviceInfo" }),
|
|
24
30
|
}));
|
|
25
31
|
|
|
26
|
-
const {
|
|
32
|
+
const mockDimensions = { width: 100, height: 200, scale: 1, fontScale: 1 };
|
|
33
|
+
|
|
34
|
+
Dimensions.get = jest.fn().mockReturnValue(mockDimensions);
|
|
35
|
+
|
|
36
|
+
Dimensions.addEventListener = jest.fn().mockReturnValue({
|
|
37
|
+
remove: jest.fn(),
|
|
38
|
+
});
|
|
27
39
|
|
|
28
40
|
describe("useDimensions", () => {
|
|
29
|
-
const mockDimensions = { width: 100, height: 200, scale: 1, fontScale: 1 };
|
|
30
41
|
const mockAppData = { someData: "test" };
|
|
31
42
|
|
|
32
43
|
beforeEach(() => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
Dimensions.addEventListener = jest.fn().mockReturnValue({
|
|
37
|
-
remove: jest.fn(),
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
mockUsePickFromState.mockReturnValue({ appData: mockAppData });
|
|
41
|
-
mockUseIsScreenActive.mockReturnValue(true);
|
|
42
|
-
mockGetInitialDimensions.mockReturnValue(mockDimensions);
|
|
43
|
-
mockGetDeviceInfo.mockReturnValue({ deviceInfo: "testDeviceInfo" });
|
|
44
|
+
StatusBar.currentHeight = 20;
|
|
45
|
+
(usePickFromState as jest.Mock).mockReturnValue({ appData: mockAppData });
|
|
44
46
|
});
|
|
45
47
|
|
|
46
48
|
it("returns correct initial dimensions", () => {
|
|
@@ -48,12 +50,9 @@ describe("useDimensions", () => {
|
|
|
48
50
|
useDimensions("window", { fullDimensions: false })
|
|
49
51
|
);
|
|
50
52
|
|
|
51
|
-
expect(result.current).
|
|
52
|
-
...mockDimensions,
|
|
53
|
+
expect(result.current).toMatchObject({
|
|
53
54
|
statusBarHeight: StatusBar.currentHeight,
|
|
54
55
|
});
|
|
55
|
-
|
|
56
|
-
expect(mockGetInitialDimensions).toHaveBeenCalledWith("window");
|
|
57
56
|
});
|
|
58
57
|
|
|
59
58
|
it("calls handler on mount", () => {
|
|
@@ -70,7 +69,7 @@ describe("useDimensions", () => {
|
|
|
70
69
|
useDimensions("window", { fullDimensions: false })
|
|
71
70
|
);
|
|
72
71
|
|
|
73
|
-
|
|
72
|
+
(useIsScreenActive as jest.Mock).mockReturnValue(false);
|
|
74
73
|
rerender();
|
|
75
74
|
|
|
76
75
|
expect(Dimensions.addEventListener).toHaveBeenCalledWith(
|
|
@@ -84,8 +83,7 @@ describe("useDimensions", () => {
|
|
|
84
83
|
useDimensions("window", { fullDimensions: true })
|
|
85
84
|
);
|
|
86
85
|
|
|
87
|
-
expect(result.current).
|
|
88
|
-
...mockDimensions,
|
|
86
|
+
expect(result.current).toMatchObject({
|
|
89
87
|
scale: 1,
|
|
90
88
|
fontScale: 1,
|
|
91
89
|
statusBarHeight: StatusBar.currentHeight,
|
|
@@ -98,7 +96,7 @@ describe("useDimensions", () => {
|
|
|
98
96
|
);
|
|
99
97
|
|
|
100
98
|
expect(result.current.height).toBe(
|
|
101
|
-
mockDimensions.height - StatusBar
|
|
99
|
+
mockDimensions.height - (StatusBar?.currentHeight ?? 0)
|
|
102
100
|
);
|
|
103
101
|
});
|
|
104
102
|
|
|
@@ -10,7 +10,7 @@ import { isTV } from "../../../reactUtils";
|
|
|
10
10
|
import { Options, UseDimensions } from "../types";
|
|
11
11
|
import { getDeviceInfo } from "../getDeviceInfo";
|
|
12
12
|
import { getInitialDimensions } from "./helpers";
|
|
13
|
-
import { useIsScreenActive } from "../../navigation";
|
|
13
|
+
import { useIsScreenActive } from "../../navigation/useIsScreenActive";
|
|
14
14
|
|
|
15
15
|
function compensateForScaleIfNeeded(context) {
|
|
16
16
|
return function () {
|
|
@@ -24,8 +24,6 @@ const applyScaleToDimensions = R.unless(R.propEq("scale", 1), (dimensions) => ({
|
|
|
24
24
|
scale: 1,
|
|
25
25
|
}));
|
|
26
26
|
|
|
27
|
-
const statusBarHeight = StatusBar?.currentHeight;
|
|
28
|
-
|
|
29
27
|
/**
|
|
30
28
|
* Returns React-native Dimensions object and updates it on any dimension change
|
|
31
29
|
* @param {('screen'|'window')} [context=window] - Dimensions context passed to Dimensions.get method
|
|
@@ -37,6 +35,7 @@ export const useDimensions: UseDimensions = (
|
|
|
37
35
|
context = "window",
|
|
38
36
|
fullDimensions = { fullDimensions: false, updateForInactiveScreens: true }
|
|
39
37
|
) => {
|
|
38
|
+
const statusBarHeight = StatusBar?.currentHeight;
|
|
40
39
|
const isActive = useIsScreenActive();
|
|
41
40
|
const { appData } = usePickFromState(["appData"]);
|
|
42
41
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/* eslint-disable no-redeclare */
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
useAppSelector,
|
|
4
|
+
selectLayoutVersion,
|
|
5
|
+
} from "@applicaster/zapp-react-native-redux";
|
|
4
6
|
|
|
5
7
|
export function useLayoutVersion(): ZappLayoutVersions;
|
|
6
8
|
|
|
@@ -23,9 +25,7 @@ export function useLayoutVersion({
|
|
|
23
25
|
isV2?: boolean;
|
|
24
26
|
isV1?: boolean;
|
|
25
27
|
} = {}): boolean | ZappLayoutVersions {
|
|
26
|
-
const layoutVersion =
|
|
27
|
-
R.path(["appData", "layoutVersion"])
|
|
28
|
-
);
|
|
28
|
+
const layoutVersion = useAppSelector(selectLayoutVersion);
|
|
29
29
|
|
|
30
30
|
if (isV2) {
|
|
31
31
|
return layoutVersion === "v2";
|
|
@@ -14,6 +14,10 @@ jest.mock("@applicaster/zapp-react-native-utils/localizationUtils", () => ({
|
|
|
14
14
|
|
|
15
15
|
jest.mock("@applicaster/zapp-react-native-utils/reactHooks/navigation");
|
|
16
16
|
|
|
17
|
+
jest.mock(
|
|
18
|
+
"@applicaster/zapp-react-native-utils/reactHooks/navigation/useNavigation"
|
|
19
|
+
);
|
|
20
|
+
|
|
17
21
|
const { useCellResolver } = require("../useCellResolver");
|
|
18
22
|
|
|
19
23
|
describe("cellResolver", () => {
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useAppSelector,
|
|
3
|
+
selectRivers,
|
|
4
|
+
} from "@applicaster/zapp-react-native-redux";
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const { rivers } = usePickFromState(riversSelector as any);
|
|
7
|
-
|
|
8
|
-
return rivers;
|
|
9
|
-
};
|
|
6
|
+
export function useRivers(): Record<string, ZappRiver> {
|
|
7
|
+
return useAppSelector(selectRivers);
|
|
8
|
+
}
|