@applicaster/zapp-react-native-utils 12.0.0-rc.7 → 12.0.0-rc.70
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/actionsExecutor/ActionExecutorContext.tsx +16 -8
- package/analyticsUtils/AnalyticsEvents/index.ts +6 -0
- package/analyticsUtils/AnalyticsEvents/searchEvents.ts +21 -0
- package/analyticsUtils/__tests__/analyticsManager.test.js +75 -73
- package/analyticsUtils/analyticsPlugins.ts +25 -0
- package/analyticsUtils/events.ts +6 -0
- package/analyticsUtils/helpers/hooks.ts +1 -1
- package/analyticsUtils/index.tsx +6 -0
- package/appUtils/HooksManager/index.js +3 -1
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +2 -3
- package/appUtils/focusManager/__tests__/focusManager.test.js +4 -24
- package/appUtils/focusManager/__tests__/index.test.ts +2 -3
- package/appUtils/focusManager/index.ts +76 -56
- package/appUtils/focusManager/treeDataStructure/Utils/index.js +74 -34
- package/appUtils/focusManager/utils/index.ts +100 -0
- package/appUtils/focusManagerAux/index.ts +76 -0
- package/appUtils/focusManagerAux/utils/index.ts +100 -0
- package/appUtils/keyCodes/__tests__/__snapshots__/keyCodes.test.js.snap +1 -1
- package/appUtils/keyCodes/__tests__/keyCodes.test.js +80 -85
- package/appUtils/keyCodes/keys.web.js +1 -0
- package/appUtils/orientationHelper.ts +1 -1
- package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +53 -0
- package/appUtils/playerManager/conts.ts +5 -0
- package/appUtils/playerManager/player.ts +47 -4
- package/appUtils/playerManager/playerFactory.ts +15 -1
- package/appUtils/playerManager/playerNative.ts +35 -0
- package/appUtils/playerManager/usePlayerControllerSetup.tsx +6 -1
- package/appUtils/playerManager/usePlayerState.tsx +13 -1
- package/appUtils/playerManager/usePlayerTitleSummaryOverlay.tsx +50 -0
- package/arrayUtils/__tests__/isFilledArray.test.ts +63 -0
- package/arrayUtils/index.ts +5 -0
- package/cellUtils/__tests__/cellUtils.test.ts +48 -0
- package/cellUtils/__tests__/fixtures.ts +13 -0
- package/cellUtils/index.ts +40 -2
- package/dateUtils/index.ts +11 -0
- package/focusManager/index.ts +1 -1
- package/functionUtils/__tests__/__snapshots__/functionUtils.test.js.snap +2 -2
- package/functionUtils/__tests__/functionUtils.test.js +244 -248
- package/headersUtils/index.ts +1 -0
- package/inflatedUrlUtils/index.ts +4 -4
- package/localizationUtils/__tests__/localizationUtils.test.ts +78 -55
- package/localizationUtils/index.ts +13 -19
- package/logger/index.ts +1 -1
- package/manifestUtils/defaultManifestConfigurations/generalContent.js +6 -0
- package/manifestUtils/defaultManifestConfigurations/player.js +10 -1
- package/manifestUtils/index.js +5 -1
- package/manifestUtils/secondaryImage/index.js +18 -15
- package/modalState/index.ts +57 -0
- package/navigationUtils/index.ts +56 -7
- package/numberUtils/__tests__/isLikeNumber.test.ts +18 -0
- package/numberUtils/index.ts +9 -18
- package/package.json +3 -2
- package/playerUtils/configurationGenerator.ts +11 -1
- package/pluginUtils/index.ts +11 -7
- package/reactHooks/autoscrolling/__tests__/useTrackCurrentAutoScrollingElement.test.ts +2 -0
- package/reactHooks/autoscrolling/__tests__/{useTrackedView.test.ts → useTrackedView.test.tsx} +18 -1
- package/reactHooks/cell-click/__tests__/index.test.js +16 -3
- package/reactHooks/cell-click/index.ts +14 -14
- package/reactHooks/device/index.ts +0 -1
- package/reactHooks/device/useIsTablet.tsx +28 -3
- package/reactHooks/device/useMemoizedIsTablet.ts +1 -1
- package/reactHooks/device/useOrientation.tsx +3 -1
- package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +30 -70
- package/reactHooks/feed/__tests__/useInflatedUrl.test.ts +18 -3
- package/reactHooks/feed/useBuildPipesUrl.ts +7 -10
- package/reactHooks/feed/useInflatedUrl.ts +44 -15
- package/reactHooks/flatList/useLoadNextPageIfNeeded.ts +16 -9
- package/reactHooks/index.ts +2 -0
- package/reactHooks/layout/__tests__/{index.test.js → index.test.tsx} +66 -59
- package/reactHooks/layout/getDeviceInfo/index.ts +4 -2
- package/reactHooks/layout/index.ts +2 -1
- package/reactHooks/layout/types/index.ts +2 -0
- package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.native.test.js +11 -16
- package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.test.js +12 -14
- package/reactHooks/layout/useDimensions/useDimensions.ts +13 -7
- package/reactHooks/navigation/__mocks__/index.ts +15 -0
- package/reactHooks/navigation/__mocks__/useNavigation.ts +7 -0
- package/reactHooks/navigation/__tests__/index.test.tsx +240 -81
- package/reactHooks/navigation/index.ts +234 -81
- package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +3 -1
- package/reactHooks/screen/__mocks__/useScreenContext.ts +3 -1
- package/reactHooks/screen/__tests__/useCurrentScreenData.test.tsx +21 -23
- package/reactHooks/screen/index.ts +4 -0
- package/reactHooks/screen/useScreenBackgroundColor.ts +23 -0
- package/reactHooks/screen/useScreenContext.ts +4 -4
- package/reactHooks/screen/useScreenData.ts +1 -1
- package/reactHooks/screen/useTargetScreenData.ts +7 -0
- package/reactHooks/state/useRivers.ts +3 -1
- package/reactHooks/useListenEventBusEvent.ts +1 -4
- package/reactHooks/videoModal/hooks/useVideoModalScreenData.tsx +10 -9
- package/riverComponetsMeasurementProvider/index.tsx +2 -2
- package/stringUtils/index.ts +5 -0
- package/testUtils/getByTestId.ts +14 -0
- package/testUtils/index.tsx +38 -23
- package/types.ts +4 -0
- package/reactHooks/device/useIsTablet.android.tsx +0 -14
- package/reactHooks/device/useIsTablet.ios.tsx +0 -7
- package/reactHooks/device/useIsTablet.web.tsx +0 -3
|
@@ -11,11 +11,9 @@ import * as QuickBrickManager from "@applicaster/zapp-react-native-bridge/QuickB
|
|
|
11
11
|
import { showConfirmationDialog } from "../alertUtils";
|
|
12
12
|
import { createCloudEvent, sendCloudEvent } from "../cloudEventsUtils";
|
|
13
13
|
import { createLogger } from "../logger";
|
|
14
|
-
import {
|
|
15
|
-
StorageMultiSelectProvider
|
|
16
|
-
} from "@applicaster/zapp-react-native-bridge/ZappStorage/StorageMultiSelectProvider";
|
|
14
|
+
import { StorageMultiSelectProvider } from "@applicaster/zapp-react-native-bridge/ZappStorage/StorageMultiSelectProvider";
|
|
17
15
|
|
|
18
|
-
const { log_error,
|
|
16
|
+
const { log_error, log_info } = createLogger({
|
|
19
17
|
subsystem: "ActionExecutorContext",
|
|
20
18
|
category: "General",
|
|
21
19
|
});
|
|
@@ -110,11 +108,17 @@ const prepareDefaultActions = (actionExecutor) => {
|
|
|
110
108
|
const tag = entry?.extensions?.tag;
|
|
111
109
|
|
|
112
110
|
if (keyNamespace && tag) {
|
|
113
|
-
const multiSelectProvider =
|
|
111
|
+
const multiSelectProvider =
|
|
112
|
+
StorageMultiSelectProvider.getProvider(keyNamespace);
|
|
114
113
|
|
|
115
114
|
const selectedItems = await multiSelectProvider.getSelectedAsync();
|
|
116
115
|
const isTagInSelectedItems = selectedItems.includes(tag);
|
|
117
|
-
|
|
116
|
+
|
|
117
|
+
log_info(
|
|
118
|
+
`handleAction: localStorageToggleFlag event will ${
|
|
119
|
+
isTagInSelectedItems ? "remove" : "add"
|
|
120
|
+
} tag: ${tag} for keyNamespace: ${keyNamespace}, current selectedItems: ${selectedItems}`
|
|
121
|
+
);
|
|
118
122
|
|
|
119
123
|
if (selectedItems.includes(tag)) {
|
|
120
124
|
await multiSelectProvider.removeItem(tag);
|
|
@@ -122,13 +126,17 @@ const prepareDefaultActions = (actionExecutor) => {
|
|
|
122
126
|
await multiSelectProvider.addItem(tag);
|
|
123
127
|
}
|
|
124
128
|
} else {
|
|
125
|
-
log_error(
|
|
129
|
+
log_error(
|
|
130
|
+
"handleAction: localStorageToggleFlag event missing keyNamespace or tag",
|
|
131
|
+
{ keyNamespace, tag }
|
|
132
|
+
);
|
|
126
133
|
|
|
127
134
|
return ActionResult.Error;
|
|
128
135
|
}
|
|
129
136
|
|
|
130
137
|
return ActionResult.Success;
|
|
131
|
-
}
|
|
138
|
+
}
|
|
139
|
+
);
|
|
132
140
|
};
|
|
133
141
|
|
|
134
142
|
export const ActionExecutorContext =
|
|
@@ -7,6 +7,10 @@ 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";
|
|
10
14
|
|
|
11
15
|
export {
|
|
12
16
|
sendScreenEvent,
|
|
@@ -18,4 +22,6 @@ export {
|
|
|
18
22
|
sendHeaderClickEvent,
|
|
19
23
|
sendHardwareBackButtonClickEvent,
|
|
20
24
|
sendDownloadsEvent,
|
|
25
|
+
sendSearchCanceledEvent,
|
|
26
|
+
sendSearchExecutedEvent,
|
|
21
27
|
};
|
|
@@ -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
|
+
};
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
+
import * as ReactNative from "react-native";
|
|
2
|
+
|
|
1
3
|
const mock_postEvent = jest.fn();
|
|
2
4
|
const mock_setPlugins = jest.fn();
|
|
3
5
|
const mock_postTimedEvent = jest.fn();
|
|
4
6
|
const mock_endTimedEvent = jest.fn();
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
},
|
|
15
|
-
}));
|
|
8
|
+
const AnalyticsBridge = {
|
|
9
|
+
postEvent: mock_postEvent,
|
|
10
|
+
setPlugins: mock_setPlugins,
|
|
11
|
+
postTimedEvent: mock_postTimedEvent,
|
|
12
|
+
endTimedEvent: mock_endTimedEvent,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
ReactNative.NativeModules.AnalyticsBridge = AnalyticsBridge;
|
|
16
16
|
|
|
17
17
|
const eventName = "event";
|
|
18
18
|
const payload = {};
|
|
@@ -26,90 +26,92 @@ function clearAllMocks() {
|
|
|
26
26
|
mock_endTimedEvent.mockClear();
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
describe("
|
|
30
|
-
|
|
29
|
+
describe("AnalyticsManager", () => {
|
|
30
|
+
describe("isValidAnalyticsData", () => {
|
|
31
|
+
beforeEach(clearAllMocks);
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
it("returns false if eventName is not a string", () => {
|
|
34
|
+
expect(analyticsManager.isValidAnalyticsData(42)).toBe(false);
|
|
35
|
+
expect(analyticsManager.isValidAnalyticsData({})).toBe(false);
|
|
36
|
+
expect(analyticsManager.isValidAnalyticsData([])).toBe(false);
|
|
37
|
+
expect(analyticsManager.isValidAnalyticsData(() => ({}))).toBe(false);
|
|
38
|
+
});
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
it("returns false if payload is not an object", () => {
|
|
41
|
+
expect(analyticsManager.isValidAnalyticsData(eventName, "string")).toBe(
|
|
42
|
+
false
|
|
43
|
+
);
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
expect(analyticsManager.isValidAnalyticsData(eventName, 42)).toBe(false);
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
expect(analyticsManager.isValidAnalyticsData(eventName, () => ({}))).toBe(
|
|
48
|
+
false
|
|
49
|
+
);
|
|
50
|
+
});
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
it("returns true if eventName is a string and payload is null or an object", () => {
|
|
53
|
+
expect(analyticsManager.isValidAnalyticsData(eventName)).toBe(true);
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
expect(analyticsManager.isValidAnalyticsData(eventName, payload)).toBe(
|
|
56
|
+
true
|
|
57
|
+
);
|
|
58
|
+
});
|
|
57
59
|
});
|
|
58
|
-
});
|
|
59
60
|
|
|
60
|
-
describe("postAnalyticEvent", () => {
|
|
61
|
-
|
|
61
|
+
describe("postAnalyticEvent", () => {
|
|
62
|
+
beforeEach(clearAllMocks);
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
it("calls the postEvent method of the analytic bridge", () => {
|
|
65
|
+
analyticsManager.postAnalyticEvent(eventName, payload);
|
|
66
|
+
expect(mock_postEvent).toHaveBeenCalledWith(eventName, payload);
|
|
67
|
+
});
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
it("skips the event if it is not valid", () => {
|
|
70
|
+
analyticsManager.postAnalyticEvent(42);
|
|
71
|
+
expect(mock_postTimedEvent).not.toHaveBeenCalled();
|
|
72
|
+
});
|
|
71
73
|
});
|
|
72
|
-
});
|
|
73
74
|
|
|
74
|
-
describe("providePlugins", () => {
|
|
75
|
-
|
|
75
|
+
describe("providePlugins", () => {
|
|
76
|
+
beforeEach(clearAllMocks);
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
it("calls the setPlugins method of the analytic bridge", () => {
|
|
79
|
+
const plugins = [];
|
|
80
|
+
const plugin_configurations = {};
|
|
81
|
+
const callback = jest.fn();
|
|
81
82
|
|
|
82
|
-
|
|
83
|
+
analyticsManager.providePlugins(plugins, plugin_configurations, callback);
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
expect(mock_setPlugins).toHaveBeenCalledWith(
|
|
86
|
+
plugins,
|
|
87
|
+
plugin_configurations,
|
|
88
|
+
callback
|
|
89
|
+
);
|
|
90
|
+
});
|
|
89
91
|
});
|
|
90
|
-
});
|
|
91
92
|
|
|
92
|
-
describe("startAnalyticsTimedEvent", () => {
|
|
93
|
-
|
|
93
|
+
describe("startAnalyticsTimedEvent", () => {
|
|
94
|
+
beforeEach(clearAllMocks);
|
|
94
95
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
it("skips the event if it is not valid", () => {
|
|
97
|
+
analyticsManager.startAnalyticsTimedEvent(42);
|
|
98
|
+
expect(mock_postTimedEvent).not.toHaveBeenCalled();
|
|
99
|
+
});
|
|
98
100
|
});
|
|
99
|
-
});
|
|
100
101
|
|
|
101
|
-
describe("endAnalyticsTimedEvent", () => {
|
|
102
|
-
|
|
102
|
+
describe("endAnalyticsTimedEvent", () => {
|
|
103
|
+
beforeEach(clearAllMocks);
|
|
103
104
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
it("emits a time_on_screen event and adds duration to payload", () => {
|
|
106
|
+
analyticsManager.endAnalyticsTimedEvent(eventName, payload);
|
|
107
|
+
const newEventName = "time_on_screen";
|
|
108
|
+
payload.duration = "N/A";
|
|
109
|
+
expect(mock_postEvent).toHaveBeenCalledWith(newEventName, payload);
|
|
110
|
+
});
|
|
110
111
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
it("skips the event if it is not valid", () => {
|
|
113
|
+
analyticsManager.endAnalyticsTimedEvent(42);
|
|
114
|
+
expect(mock_postEvent).not.toHaveBeenCalled();
|
|
115
|
+
});
|
|
114
116
|
});
|
|
115
117
|
});
|
|
@@ -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
|
+
};
|
package/analyticsUtils/events.ts
CHANGED
|
@@ -23,10 +23,16 @@ 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
|
+
|
|
26
31
|
export const ANALYTICS_CORE_EVENTS = {
|
|
27
32
|
...LEGACY_EVENTS,
|
|
28
33
|
...SCREEN_VIEW_EVENTS,
|
|
29
34
|
...TAPPING_EVENTS,
|
|
35
|
+
...SEARCH_EVENTS,
|
|
30
36
|
};
|
|
31
37
|
|
|
32
38
|
export const WEBVIEW_EVENTS = {
|
|
@@ -26,7 +26,7 @@ export const useHookAnalytics = (props): void => {
|
|
|
26
26
|
if (screenData?.hookPlugin?.module?.runInBackground) {
|
|
27
27
|
sendScreenEvent({ analyticsScreenData });
|
|
28
28
|
}
|
|
29
|
-
}, [hookPlugin.screen_id]);
|
|
29
|
+
}, [sendScreenEvent, screenData, hookPlugin.screen_id]);
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
export const useScreenAnalytics = (props) => {
|
package/analyticsUtils/index.tsx
CHANGED
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
sendHardwareBackButtonClickEvent,
|
|
19
19
|
sendDownloadsEvent,
|
|
20
20
|
sendScreenEvent,
|
|
21
|
+
sendSearchCanceledEvent,
|
|
22
|
+
sendSearchExecutedEvent,
|
|
21
23
|
} from "./AnalyticsEvents";
|
|
22
24
|
|
|
23
25
|
import { postAnalyticEvent } from "./manager";
|
|
@@ -66,6 +68,8 @@ type AnalyticsFunctions = {
|
|
|
66
68
|
item: ZappEntry;
|
|
67
69
|
state: OfflineItemState;
|
|
68
70
|
}) => void;
|
|
71
|
+
sendSearchCanceledEvent: (props: AnalyticsDefaultProperties) => void;
|
|
72
|
+
sendSearchExecutedEvent: (props: AnalyticsDefaultProperties) => void;
|
|
69
73
|
};
|
|
70
74
|
|
|
71
75
|
type GetAnalyticsFunctions = (
|
|
@@ -102,6 +106,8 @@ export function getAnalyticsFunctions({
|
|
|
102
106
|
sendHardwareBackButtonClickEvent,
|
|
103
107
|
sendScreenEvent,
|
|
104
108
|
sendDownloadsEvent,
|
|
109
|
+
sendSearchCanceledEvent,
|
|
110
|
+
sendSearchExecutedEvent,
|
|
105
111
|
};
|
|
106
112
|
}
|
|
107
113
|
|
|
@@ -270,7 +270,9 @@ export function HooksManager({
|
|
|
270
270
|
});
|
|
271
271
|
|
|
272
272
|
const isHookInHomescreen = payload?.home;
|
|
273
|
-
|
|
273
|
+
|
|
274
|
+
const isHookFlowBlocker =
|
|
275
|
+
hookPlugin?.module?.isFlowBlocker?.() ?? false;
|
|
274
276
|
|
|
275
277
|
// TODO: We technically need to also support the case where there is more than one hook in the stack,
|
|
276
278
|
// if needed we could add differentiation explicit user cancellation (when we should go to background) and other types of cancellation
|
|
@@ -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("
|
|
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 {
|
|
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(
|
|
93
|
+
currentFocusable.blur(direction);
|
|
97
94
|
currentFocusable.hasLostFocus(getCurrentFocus(), direction);
|
|
98
95
|
}
|
|
99
96
|
}
|
|
@@ -226,32 +223,52 @@ export const focusManager = (function () {
|
|
|
226
223
|
|
|
227
224
|
logger.log({ message: "setInitialFocus", data: { preferredFocus } });
|
|
228
225
|
|
|
229
|
-
if (preferredFocus
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if (
|
|
234
|
-
focusableItem &&
|
|
235
|
-
!focusableItem.isGroup &&
|
|
236
|
-
isFocusEnabled(focusableItem)
|
|
237
|
-
) {
|
|
238
|
-
if (initialFocusOnContent) {
|
|
239
|
-
blur();
|
|
240
|
-
initialFocusOnContent = false;
|
|
241
|
-
}
|
|
226
|
+
if (isNilOrEmpty(preferredFocus)) {
|
|
227
|
+
return { success: false };
|
|
228
|
+
}
|
|
242
229
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
data: {
|
|
246
|
-
focusableItem,
|
|
247
|
-
},
|
|
248
|
-
});
|
|
230
|
+
const focusCandidate = preferredFocus[0];
|
|
231
|
+
const focusableItem = focusCandidate.component;
|
|
249
232
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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 };
|
|
254
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 };
|
|
255
272
|
}
|
|
256
273
|
|
|
257
274
|
function treeLoaded(val, node) {
|
|
@@ -261,17 +278,8 @@ export const focusManager = (function () {
|
|
|
261
278
|
val,
|
|
262
279
|
node,
|
|
263
280
|
currentFocusNode,
|
|
264
|
-
initialFocusOnContent,
|
|
265
281
|
},
|
|
266
282
|
});
|
|
267
|
-
|
|
268
|
-
if ((val && !currentFocusNode) || initialFocusOnContent) {
|
|
269
|
-
logger.log({ message: `focusAutoEnabled ${focusAutoEnabled}` });
|
|
270
|
-
|
|
271
|
-
if (focusAutoEnabled) {
|
|
272
|
-
setInitialFocus(initialFocusOnContent ? node : undefined);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
283
|
}
|
|
276
284
|
|
|
277
285
|
/**
|
|
@@ -402,11 +410,21 @@ export const focusManager = (function () {
|
|
|
402
410
|
group ? unregisterGroup(id) : unregisterItem(id);
|
|
403
411
|
}
|
|
404
412
|
|
|
405
|
-
function
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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();
|
|
410
428
|
}
|
|
411
429
|
}
|
|
412
430
|
|
|
@@ -416,6 +434,11 @@ export const focusManager = (function () {
|
|
|
416
434
|
* @param {Object} direction of the scroll
|
|
417
435
|
*/
|
|
418
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
|
+
|
|
419
442
|
if (focusDisabled) return false;
|
|
420
443
|
|
|
421
444
|
if (currentFocusNode) {
|
|
@@ -444,8 +467,6 @@ export const focusManager = (function () {
|
|
|
444
467
|
} else {
|
|
445
468
|
currentFocusNode.component.failedLostFocus(currentFocusNode, direction);
|
|
446
469
|
}
|
|
447
|
-
} else {
|
|
448
|
-
setInitialFocus();
|
|
449
470
|
}
|
|
450
471
|
}
|
|
451
472
|
|
|
@@ -505,7 +526,6 @@ export const focusManager = (function () {
|
|
|
505
526
|
on,
|
|
506
527
|
invokeHandler,
|
|
507
528
|
removeHandler,
|
|
508
|
-
setupInitialFocus,
|
|
509
529
|
longPress,
|
|
510
530
|
pressOut,
|
|
511
531
|
enableFocus,
|
|
@@ -514,9 +534,9 @@ export const focusManager = (function () {
|
|
|
514
534
|
isFocusDisabled,
|
|
515
535
|
getCurrentFocusNode,
|
|
516
536
|
focusableTree,
|
|
517
|
-
disableAutomaticFocus,
|
|
518
|
-
enableAutomaticFocus,
|
|
519
537
|
getGroupRootById,
|
|
520
538
|
isGroupItemFocused,
|
|
539
|
+
recoverFocus,
|
|
540
|
+
isCurrentFocusOnTheTopScreen,
|
|
521
541
|
};
|
|
522
542
|
})();
|