@applicaster/zapp-react-native-utils 12.0.0-rc.99 → 12.0.2-alpha.5400332623

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 (47) hide show
  1. package/analyticsUtils/AnalyticPlayerListener.ts +141 -0
  2. package/analyticsUtils/PlayerAnalyticsManager.ts +90 -0
  3. package/analyticsUtils/{analyticsTracker.ts → playerAnalyticsTracker.ts} +19 -10
  4. package/appUtils/accessibilityManager/hooks.ts +16 -7
  5. package/appUtils/accessibilityManager/index.ts +33 -28
  6. package/appUtils/platform/platformUtils.ts +35 -2
  7. package/appUtils/playerManager/index.ts +9 -27
  8. package/appUtils/playerManager/playerFactory.ts +5 -4
  9. package/appUtils/playerManager/playerHooks/usePlayerCurrentTime.tsx +57 -0
  10. package/appUtils/playerManager/playerNative.ts +1 -1
  11. package/appUtils/playerManager/usePlayerControllerSetup.tsx +1 -1
  12. package/appUtils/playerManager/usePlayerState.tsx +19 -34
  13. package/arrayUtils/index.ts +11 -0
  14. package/cellUtils/index.ts +38 -5
  15. package/focusManager/FocusManager.ts +20 -5
  16. package/focusManager/__tests__/FocusManager.test.ts +0 -1
  17. package/functionUtils/index.ts +38 -0
  18. package/index.d.ts +15 -0
  19. package/manifestUtils/defaultManifestConfigurations/player.js +121 -0
  20. package/manifestUtils/keys.js +2 -1
  21. package/navigationUtils/index.ts +1 -1
  22. package/numberUtils/__tests__/toNumber.test.ts +2 -2
  23. package/numberUtils/__tests__/toPositiveNumberWithDefault.test.ts +66 -0
  24. package/numberUtils/__tests__/toPositiveNumberWithDefaultZero.test.ts +66 -0
  25. package/numberUtils/index.ts +20 -0
  26. package/package.json +2 -2
  27. package/playerUtils/configurationGenerator.ts +120 -0
  28. package/playerUtils/imaScriptDom.ts +59 -0
  29. package/reactHooks/cell-click/index.ts +17 -3
  30. package/reactHooks/debugging/__tests__/index.test.js +81 -11
  31. package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +69 -6
  32. package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +10 -2
  33. package/reactHooks/feed/useBatchLoading.ts +52 -28
  34. package/reactHooks/feed/useFeedLoader.tsx +11 -7
  35. package/reactHooks/layout/useDimensions/__tests__/useDimensions.test.ts +134 -0
  36. package/reactHooks/layout/useDimensions/useDimensions.ts +2 -0
  37. package/reactHooks/navigation/index.ts +1 -1
  38. package/reactHooks/player/useAutoSeek.tsx +9 -7
  39. package/reactHooks/resolvers/useCellResolver.ts +5 -1
  40. package/reactHooks/useConfiguration.ts +2 -2
  41. package/reactHooks/utils/index.ts +13 -0
  42. package/reactUtils/index.ts +1 -2
  43. package/services/localStorageServiceSync.web.ts +48 -0
  44. package/services/sessionStorageServiceSync.web.ts +32 -0
  45. package/services/storageServiceSync.web.ts +24 -0
  46. package/services/storageWebUtils.web.ts +10 -0
  47. package/testUtils/index.tsx +42 -9
@@ -1,12 +1,11 @@
1
1
  import React, { useEffect } from "react";
2
- import { path } from "ramda";
3
2
  import { useDispatch } from "react-redux";
4
3
 
5
4
  import { loadPipesData } from "@applicaster/zapp-react-native-redux/ZappPipes";
6
5
  import { useZappPipesFeed } from "@applicaster/zapp-react-native-redux/hooks";
7
6
 
8
7
  import { reactHooksLogger } from "../logger";
9
- import { useIsInitialRender } from "../utils";
8
+ import { useIsInitialRender, shouldDispatchData } from "../utils";
10
9
  import { useInflatedUrl } from "./useInflatedUrl";
11
10
  import { useRoute } from "../navigation";
12
11
 
@@ -56,8 +55,6 @@ export const useFeedLoader = ({
56
55
 
57
56
  const currentFeed = useZappPipesFeed(callableFeedUrl);
58
57
 
59
- const cachedFeed = React.useMemo(() => path(["data"])(currentFeed), []);
60
-
61
58
  const riverId = screenData?.id;
62
59
 
63
60
  const reloadData = React.useCallback<ReloadDataFunction>(
@@ -92,11 +89,18 @@ export const useFeedLoader = ({
92
89
  }
93
90
  }, [callableFeedUrl, currentFeed?.data]);
94
91
 
95
- // If feed doesn't exist, load it
96
92
  useEffect(() => {
97
- if (!cachedFeed || pipesOptions.clearCache) {
93
+ if (
94
+ shouldDispatchData(callableFeedUrl, currentFeed, pipesOptions.clearCache)
95
+ ) {
98
96
  if (callableFeedUrl) {
99
- dispatch(loadPipesData(callableFeedUrl, { ...pipesOptions, riverId }));
97
+ dispatch(
98
+ loadPipesData(callableFeedUrl, {
99
+ ...pipesOptions,
100
+ clearCache: true,
101
+ riverId,
102
+ })
103
+ );
100
104
  } else {
101
105
  logger.info({
102
106
  message:
@@ -0,0 +1,134 @@
1
+ import { renderHook } from "@testing-library/react-hooks";
2
+ import { Dimensions, StatusBar } from "react-native";
3
+
4
+ const mockUsePickFromState = jest.fn();
5
+ const mockUseIsScreenActive = jest.fn();
6
+ const mockGetInitialDimensions = jest.fn();
7
+ const mockGetDeviceInfo = jest.fn();
8
+
9
+ jest.mock("@applicaster/zapp-react-native-redux/hooks", () => ({
10
+ ...(jest.requireActual("@applicaster/zapp-react-native-redux/hooks") as {}),
11
+ usePickFromState: mockUsePickFromState,
12
+ }));
13
+
14
+ jest.mock("../../../navigation", () => ({
15
+ useIsScreenActive: mockUseIsScreenActive,
16
+ }));
17
+
18
+ jest.mock("../helpers", () => ({
19
+ getInitialDimensions: mockGetInitialDimensions,
20
+ }));
21
+
22
+ jest.mock("../../getDeviceInfo", () => ({
23
+ getDeviceInfo: mockGetDeviceInfo,
24
+ }));
25
+
26
+ const { useDimensions } = require("../useDimensions");
27
+
28
+ describe("useDimensions", () => {
29
+ const mockDimensions = { width: 100, height: 200, scale: 1, fontScale: 1 };
30
+ const mockAppData = { someData: "test" };
31
+
32
+ beforeEach(() => {
33
+ jest.clearAllMocks();
34
+ Dimensions.get = jest.fn().mockReturnValue(mockDimensions);
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
+ });
45
+
46
+ it("returns correct initial dimensions", () => {
47
+ const { result } = renderHook(() =>
48
+ useDimensions("window", { fullDimensions: false })
49
+ );
50
+
51
+ expect(result.current).toEqual({
52
+ ...mockDimensions,
53
+ statusBarHeight: StatusBar.currentHeight,
54
+ });
55
+
56
+ expect(mockGetInitialDimensions).toHaveBeenCalledWith("window");
57
+ });
58
+
59
+ it("calls handler on mount", () => {
60
+ renderHook(() => useDimensions("window", { fullDimensions: false }));
61
+
62
+ expect(Dimensions.addEventListener).toHaveBeenCalledWith(
63
+ "change",
64
+ expect.any(Function)
65
+ );
66
+ });
67
+
68
+ it("calls handler on isActive change", () => {
69
+ const { rerender } = renderHook(() =>
70
+ useDimensions("window", { fullDimensions: false })
71
+ );
72
+
73
+ mockUseIsScreenActive.mockReturnValue(false);
74
+ rerender();
75
+
76
+ expect(Dimensions.addEventListener).toHaveBeenCalledWith(
77
+ "change",
78
+ expect.any(Function)
79
+ );
80
+ });
81
+
82
+ it("handles fullDimensions option", () => {
83
+ const { result } = renderHook(() =>
84
+ useDimensions("window", { fullDimensions: true })
85
+ );
86
+
87
+ expect(result.current).toEqual({
88
+ ...mockDimensions,
89
+ scale: 1,
90
+ fontScale: 1,
91
+ statusBarHeight: StatusBar.currentHeight,
92
+ });
93
+ });
94
+
95
+ it("handles excludeStatusBar option", () => {
96
+ const { result } = renderHook(() =>
97
+ useDimensions("window", { excludeStatusBar: true })
98
+ );
99
+
100
+ expect(result.current.height).toBe(
101
+ mockDimensions.height - StatusBar.currentHeight ?? 0
102
+ );
103
+ });
104
+
105
+ it("handles rounded option", () => {
106
+ const { result } = renderHook(() =>
107
+ useDimensions("window", { rounded: true })
108
+ );
109
+
110
+ expect(result.current.width).toBe(Math.ceil(mockDimensions.width));
111
+ expect(result.current.height).toBe(Math.ceil(mockDimensions.height));
112
+ });
113
+
114
+ it("handles deviceInfo option", () => {
115
+ const { result } = renderHook(() =>
116
+ useDimensions("window", { deviceInfo: true })
117
+ );
118
+
119
+ expect(result.current.deviceInfo).toMatchObject({
120
+ deviceInfo: "testDeviceInfo",
121
+ });
122
+ });
123
+
124
+ it("logs deprecation warning when fullDimensions is boolean", () => {
125
+ const consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation();
126
+ renderHook(() => useDimensions("window", true));
127
+
128
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
129
+ "Deprecation Warning\nSecond argument is now the options object. {fullDimensions: boolean, ...}"
130
+ );
131
+
132
+ consoleWarnSpy.mockRestore();
133
+ });
134
+ });
@@ -95,6 +95,8 @@ export const useDimensions: UseDimensions = (
95
95
 
96
96
  const activeSubscription = Dimensions.addEventListener("change", handler);
97
97
 
98
+ handler({ [context]: Dimensions.get(context) });
99
+
98
100
  return () => {
99
101
  activeSubscription.remove();
100
102
  };
@@ -17,7 +17,7 @@ import {
17
17
  QUICK_BRICK_NAVBAR,
18
18
  QUICK_BRICK_CONTENT,
19
19
  } from "@applicaster/quick-brick-core/const";
20
- import { useZappHooksModalState } from "@applicaster/zapp-react-dom-ui-components/Components/ZappHooksModal/ZappHooksModal";
20
+ import { useZappHooksModalState } from "@applicaster/zapp-react-dom-ui-components/Components/ZappHooksModal/hooks/useZappHooksModalState";
21
21
  import { ZappHookModalContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
22
22
  import { PathnameContext } from "@applicaster/zapp-react-native-ui-components/Contexts/PathnameContext";
23
23
  import { HookModalContextT } from "@applicaster/zapp-react-native-ui-components/Contexts/ZappHookModalContext";
@@ -1,5 +1,4 @@
1
1
  import React, { useEffect, useState } from "react";
2
- import * as R from "ramda";
3
2
  import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
4
3
  import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager";
5
4
  import {
@@ -17,10 +16,6 @@ export const useAutoSeek = (resetHUDTimer = noop) => {
17
16
  const intervalTimer = React.useRef(null);
18
17
  const prevRunning = usePrevious(isRunning);
19
18
 
20
- const setIntervalTimer = (cb) => {
21
- intervalTimer.current = setInterval(cb, ON_HOLD_INTERVAL);
22
- };
23
-
24
19
  const clearIntervalTimer = () => {
25
20
  try {
26
21
  clearInterval(intervalTimer.current);
@@ -29,6 +24,11 @@ export const useAutoSeek = (resetHUDTimer = noop) => {
29
24
  }
30
25
  };
31
26
 
27
+ const setIntervalTimer = (cb) => {
28
+ clearIntervalTimer();
29
+ intervalTimer.current = setInterval(cb, ON_HOLD_INTERVAL);
30
+ };
31
+
32
32
  const resetState = () => {
33
33
  setIsRunning(false);
34
34
  setDirection(null);
@@ -42,7 +42,7 @@ export const useAutoSeek = (resetHUDTimer = noop) => {
42
42
  if (!player) return;
43
43
 
44
44
  if (direction) {
45
- player[direction](R.min(MAX_SEEK_TIME, SKIP_TIME_BASE * multiplier));
45
+ player[direction](Math.min(MAX_SEEK_TIME, SKIP_TIME_BASE * multiplier));
46
46
  resetHUDTimer();
47
47
  }
48
48
  }, [multiplier, direction]);
@@ -50,7 +50,9 @@ export const useAutoSeek = (resetHUDTimer = noop) => {
50
50
  useEffect(() => {
51
51
  if (isRunning && isRunning !== prevRunning) {
52
52
  setIntervalTimer(() => {
53
- setMultiplier(R.add(1));
53
+ setMultiplier((prev) => {
54
+ return prev + 1;
55
+ });
54
56
  });
55
57
  } else if (!isRunning && isRunning !== prevRunning) {
56
58
  resetState();
@@ -48,7 +48,11 @@ export function useCellResolver({
48
48
  ...cellResolverOptions
49
49
  }: Props) {
50
50
  let _component = Object.assign({}, component);
51
- const { width, height } = useDimensions("screen");
51
+
52
+ const { width, height } = useDimensions("screen", {
53
+ updateForInactiveScreens: false,
54
+ });
55
+
52
56
  const { plugins, cellStyles } = usePickFromState(["plugins", "cellStyles"]);
53
57
  const isRTL = useIsRTL();
54
58
 
@@ -67,7 +67,7 @@ export const useConfiguration = ({
67
67
  if (component) {
68
68
  const remappedStylesConfiguration = remapUpdatedKeys(
69
69
  pluginManifest,
70
- component.styles
70
+ component
71
71
  );
72
72
 
73
73
  R.forEachObjIndexed((value, key) => {
@@ -83,7 +83,7 @@ export const useConfiguration = ({
83
83
 
84
84
  const styles = populateConfigurationValues(
85
85
  pluginManifest?.styles?.fields,
86
- remappedStylesConfiguration,
86
+ remappedStylesConfiguration.styles,
87
87
  skipDefaults
88
88
  );
89
89
 
@@ -30,3 +30,16 @@ export const useIsInitialRender = (): boolean => {
30
30
 
31
31
  return isInitialRenderRef.current;
32
32
  };
33
+
34
+ export const checkIsLocalFeed = (feedUrl) => feedUrl?.startsWith("pipesv2://");
35
+
36
+ export const shouldDispatchData = (
37
+ url: string,
38
+ feed: ZappPipesData | null,
39
+ clearCache: boolean
40
+ ) => {
41
+ const currentFeedHasData = feed?.data;
42
+ const isLocalFeed = checkIsLocalFeed(url);
43
+
44
+ return !currentFeedHasData || clearCache || isLocalFeed;
45
+ };
@@ -152,8 +152,7 @@ export const hasTizen = typeof window?.tizen !== "undefined";
152
152
  */
153
153
  export const hasWebOS = typeof window?.webOS !== "undefined";
154
154
 
155
- export const getUserAgent = () =>
156
- window?.navigator?.userAgent || global?.navigator?.userAgent;
155
+ export const getUserAgent = () => window?.navigator?.userAgent;
157
156
 
158
157
  export const isLgPlatform = () => {
159
158
  const userAgent = getUserAgent();
@@ -0,0 +1,48 @@
1
+ import {
2
+ DEFAULT_STORAGE_NAMESPACE,
3
+ getWebStorageKey,
4
+ NAMESPACE_STORAGE_SEPARATOR_WEB,
5
+ } from "./storageWebUtils.web";
6
+
7
+ export function saveSync(
8
+ key: string,
9
+ value: string,
10
+ namespace: string = DEFAULT_STORAGE_NAMESPACE
11
+ ) {
12
+ const storageWebKey = getWebStorageKey(key, namespace);
13
+
14
+ return localStorage.setItem(storageWebKey, value);
15
+ }
16
+
17
+ export function getSync(
18
+ key: string,
19
+ namespace: string = DEFAULT_STORAGE_NAMESPACE
20
+ ) {
21
+ const storageWebKey = getWebStorageKey(key, namespace);
22
+
23
+ return localStorage.getItem(storageWebKey);
24
+ }
25
+
26
+ export function getAllSync(namespace: string = DEFAULT_STORAGE_NAMESPACE) {
27
+ const allItems = {};
28
+
29
+ for (let i = 0; i < localStorage.length; i++) {
30
+ const key = localStorage.key(i);
31
+
32
+ if (key && key.startsWith(namespace + NAMESPACE_STORAGE_SEPARATOR_WEB)) {
33
+ const itemKey = key.split(NAMESPACE_STORAGE_SEPARATOR_WEB)[1];
34
+ allItems[itemKey] = localStorage.getItem(key);
35
+ }
36
+ }
37
+
38
+ return allItems;
39
+ }
40
+
41
+ export function removeSync(
42
+ key: string,
43
+ namespace: string = DEFAULT_STORAGE_NAMESPACE
44
+ ) {
45
+ const storageWebKey = getWebStorageKey(key, namespace);
46
+
47
+ return localStorage.removeItem(storageWebKey);
48
+ }
@@ -0,0 +1,32 @@
1
+ import {
2
+ DEFAULT_STORAGE_NAMESPACE,
3
+ getWebStorageKey,
4
+ } from "./storageWebUtils.web";
5
+
6
+ export function saveSync(
7
+ key: string,
8
+ value: string,
9
+ namespace: string = DEFAULT_STORAGE_NAMESPACE
10
+ ) {
11
+ const storageWebKey = getWebStorageKey(key, namespace);
12
+
13
+ return sessionStorage.setItem(storageWebKey, value);
14
+ }
15
+
16
+ export function getSync(
17
+ key: string,
18
+ namespace: string = DEFAULT_STORAGE_NAMESPACE
19
+ ) {
20
+ const storageWebKey = getWebStorageKey(key, namespace);
21
+
22
+ return sessionStorage.getItem(storageWebKey);
23
+ }
24
+
25
+ export function removeSync(
26
+ key: string,
27
+ namespace: string = DEFAULT_STORAGE_NAMESPACE
28
+ ) {
29
+ const storageWebKey = getWebStorageKey(key, namespace);
30
+
31
+ return sessionStorage.removeItem(storageWebKey);
32
+ }
@@ -0,0 +1,24 @@
1
+ import { DEFAULT_STORAGE_NAMESPACE } from "./storageWebUtils.web";
2
+ import { getSync as getLocalStorage } from "./localStorageServiceSync.web";
3
+ import { getSync as getSessionStorage } from "./sessionStorageServiceSync.web";
4
+
5
+ export const getFromSessionOrLocalStorage = (
6
+ key: string,
7
+ namespace: string = DEFAULT_STORAGE_NAMESPACE
8
+ ) => getSessionStorage(key, namespace) || getLocalStorage(key, namespace);
9
+
10
+ export const resolveStorageContextKey = (storageKey: string) => {
11
+ if (!storageKey) {
12
+ return;
13
+ }
14
+
15
+ const splitKey = storageKey.split(".");
16
+ const namespaceOrKey = splitKey[0];
17
+ const key = splitKey[1];
18
+
19
+ if (!key) {
20
+ return getFromSessionOrLocalStorage(key);
21
+ } else {
22
+ return getFromSessionOrLocalStorage(key, namespaceOrKey);
23
+ }
24
+ };
@@ -0,0 +1,10 @@
1
+ export const DEFAULT_STORAGE_NAMESPACE = "applicaster.v2";
2
+
3
+ export const NAMESPACE_STORAGE_SEPARATOR_WEB = "_::_";
4
+
5
+ export const getWebStorageKey = (
6
+ key: String,
7
+ namespace: String = DEFAULT_STORAGE_NAMESPACE
8
+ ) => {
9
+ return `${namespace}${NAMESPACE_STORAGE_SEPARATOR_WEB}${key}`;
10
+ };
@@ -7,9 +7,11 @@ import { Provider } from "react-redux";
7
7
  import thunk from "redux-thunk";
8
8
  import configureStore from "redux-mock-store";
9
9
  import { SafeAreaProvider } from "react-native-safe-area-context";
10
+ import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
10
11
 
11
12
  import { render } from "@testing-library/react-native";
12
13
  import { AnalyticsProvider } from "../analyticsUtils";
14
+ import { ThemeContext } from "../theme";
13
15
 
14
16
  export { getByTestId } from "./getByTestId";
15
17
 
@@ -59,7 +61,9 @@ const initialState = {
59
61
  appSettings: {},
60
62
  plugins: [],
61
63
  appState: { appReady: true, appLaunched: true },
64
+ pluginConfigurations: {},
62
65
  appData: { layoutVersion: "v1" },
66
+ zappPipes: {},
63
67
  contentTypes: null,
64
68
  };
65
69
 
@@ -68,20 +72,49 @@ export const WrappedWithProviders = ({
68
72
  store: storeObj = {},
69
73
  }: any) => {
70
74
  const _store = configureStore([thunk])(
71
- R.mergeDeepWith(R.concat, { ...initialState }, storeObj)
75
+ R.mergeDeepRight(initialState, { ...storeObj })
72
76
  );
73
77
 
78
+ appStore.set(_store);
79
+
74
80
  return (
75
- <SafeAreaProvider
76
- initialMetrics={{
77
- insets: { top: 0, left: 0, right: 0, bottom: 0 },
78
- frame: { x: 0, y: 0, width: 0, height: 0 },
81
+ <ThemeContext.Provider
82
+ value={{
83
+ themes: {
84
+ light: {
85
+ component_margin_top: 0,
86
+ component_margin_bottom: 0,
87
+ component_margin_left: 0,
88
+ component_margin_right: 0,
89
+ component_padding_top: 0,
90
+ component_padding_bottom: 0,
91
+ component_padding_left: 0,
92
+ component_padding_right: 0,
93
+ component_gutter_vertical: 0,
94
+ component_gutter_horizontal: 0,
95
+ component_corner_radius: 0,
96
+ app_background_color: "rgba(0,0,0,0)",
97
+ screen_margin_top: 0,
98
+ screen_margin_bottom: 0,
99
+ component_anchor_point_y: 0,
100
+ assets: "",
101
+ },
102
+ },
103
+ selectedThemeId: "light",
104
+ setSelectedThemeId: () => {},
79
105
  }}
80
106
  >
81
- <Provider store={_store}>
82
- <AnalyticsProvider>{children}</AnalyticsProvider>
83
- </Provider>
84
- </SafeAreaProvider>
107
+ <SafeAreaProvider
108
+ initialMetrics={{
109
+ insets: { top: 0, left: 0, right: 0, bottom: 0 },
110
+ frame: { x: 0, y: 0, width: 0, height: 0 },
111
+ }}
112
+ >
113
+ <Provider store={_store}>
114
+ <AnalyticsProvider>{children}</AnalyticsProvider>
115
+ </Provider>
116
+ </SafeAreaProvider>
117
+ </ThemeContext.Provider>
85
118
  );
86
119
  };
87
120