@applicaster/zapp-react-native-utils 13.0.0-rc.99 → 14.0.0-alpha.1216545755
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 +87 -67
- package/actionsExecutor/ScreenActions.ts +92 -0
- package/actionsExecutor/StorageActions.ts +110 -0
- package/actionsExecutor/consts.ts +4 -0
- package/actionsExecutor/feedDecorator.ts +171 -0
- package/actionsExecutor/screenResolver.ts +11 -0
- package/appUtils/__tests__/__snapshots__/localizationsHelper.test.ts.snap +151 -0
- package/appUtils/__tests__/allZappLocales.ts +79 -0
- package/appUtils/__tests__/{localizationsHelper.test.js → localizationsHelper.test.ts} +11 -0
- package/appUtils/accessibilityManager/const.ts +18 -0
- package/appUtils/accessibilityManager/index.ts +4 -1
- package/appUtils/contextKeysManager/contextResolver.ts +14 -1
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +1 -0
- package/appUtils/focusManager/index.ios.ts +14 -4
- package/appUtils/focusManager/utils/__tests__/findChild.test.ts +35 -0
- package/appUtils/focusManager/utils/index.ts +5 -0
- package/appUtils/localizationsHelper.ts +10 -2
- package/appUtils/playerManager/playerHooks/usePlayerCurrentTime.tsx +11 -7
- package/arrayUtils/__tests__/isEmptyArray.test.ts +63 -0
- package/arrayUtils/__tests__/isFilledArray.test.ts +1 -1
- package/arrayUtils/index.ts +7 -2
- package/audioPlayerUtils/__tests__/getArtworkImage.test.ts +144 -0
- package/audioPlayerUtils/__tests__/getBackgroundImage.test.ts +72 -0
- package/audioPlayerUtils/__tests__/getImageFromEntry.test.ts +110 -0
- package/audioPlayerUtils/assets/index.ts +2 -0
- package/audioPlayerUtils/index.ts +242 -0
- package/cellUtils/index.ts +9 -5
- package/componentsUtils/index.ts +8 -1
- package/conf/player/__tests__/selectors.test.ts +34 -0
- package/conf/player/selectors.ts +10 -0
- package/configurationUtils/__tests__/configurationUtils.test.js +0 -31
- package/configurationUtils/__tests__/getMediaItems.test.ts +65 -0
- package/configurationUtils/__tests__/imageSrcFromMediaItem.test.ts +34 -0
- package/configurationUtils/index.ts +63 -34
- package/localizationUtils/index.ts +3 -3
- package/manifestUtils/_internals/getDefaultConfiguration.js +28 -0
- package/manifestUtils/{_internals.js → _internals/index.js} +2 -25
- package/manifestUtils/createConfig.js +4 -1
- package/manifestUtils/defaultManifestConfigurations/generalContent.js +13 -0
- package/manifestUtils/defaultManifestConfigurations/player.js +1228 -205
- package/manifestUtils/index.js +2 -0
- package/manifestUtils/keys.js +27 -2
- package/manifestUtils/progressBar/__tests__/mobileProgressBar.test.js +0 -30
- package/manifestUtils/sharedConfiguration/screenPicker/stylesFields.js +1 -2
- package/navigationUtils/__tests__/navigationUtils.test.js +0 -65
- package/navigationUtils/index.ts +0 -31
- package/package.json +2 -2
- package/playerUtils/__tests__/configurationUtils.test.ts +1 -65
- package/playerUtils/__tests__/getPlayerActionButtons.test.ts +54 -0
- package/playerUtils/_internals/__tests__/utils.test.ts +71 -0
- package/playerUtils/_internals/index.ts +1 -0
- package/playerUtils/_internals/utils.ts +31 -0
- package/playerUtils/configurationUtils.ts +0 -44
- package/playerUtils/getPlayerActionButtons.ts +17 -0
- package/playerUtils/index.ts +25 -0
- package/playerUtils/useValidatePlayerConfig.tsx +22 -19
- package/reactHooks/app/useAppState.ts +2 -2
- package/reactHooks/cell-click/index.ts +8 -1
- package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +20 -0
- package/reactHooks/feed/useBatchLoading.ts +12 -14
- package/reactHooks/feed/useFeedLoader.tsx +12 -5
- package/reactHooks/navigation/{useGetTabBarHeight.ts → getTabBarHeight.ts} +1 -1
- package/reactHooks/navigation/useGetBottomTabBarHeight.ts +10 -3
- package/reactHooks/navigation/useNavigationPluginData.ts +8 -4
- package/reactHooks/navigation/useNavigationType.ts +4 -2
- package/reactHooks/navigation/useRoute.ts +7 -2
- package/reactHooks/navigation/useScreenStateStore.ts +11 -0
- package/reactHooks/screen/__tests__/useScreenBackgroundColor.test.tsx +69 -0
- package/reactHooks/screen/useScreenBackgroundColor.ts +3 -15
- package/reactHooks/state/README.md +79 -0
- package/reactHooks/state/ZStoreProvider.tsx +71 -0
- package/reactHooks/state/__tests__/ZStoreProvider.test.tsx +66 -0
- package/reactHooks/state/index.ts +2 -0
- package/reactHooks/useListenEventBusEvent.ts +1 -1
- package/reactUtils/index.ts +9 -0
- package/storage/ScreenSingleValueProvider.ts +198 -0
- package/storage/ScreenStateMultiSelectProvider.ts +293 -0
- package/storage/StorageMultiSelectProvider.ts +192 -0
- package/storage/StorageSingleSelectProvider.ts +108 -0
- package/typeGuards/index.ts +3 -0
- package/utils/index.ts +12 -1
- package/zappFrameworkUtils/localStorageHelper.ts +32 -10
- package/playerUtils/configurationGenerator.ts +0 -2588
|
@@ -16,7 +16,8 @@ import { ActionExecutorContext } from "@applicaster/zapp-react-native-utils/acti
|
|
|
16
16
|
import { isFunction, noop } from "../../functionUtils";
|
|
17
17
|
import { useSendAnalyticsOnPress } from "../analytics";
|
|
18
18
|
import { logOnPress, warnEmptyContentType } from "./helpers";
|
|
19
|
-
import { useCurrentScreenData } from "../screen";
|
|
19
|
+
import { useCurrentScreenData, useScreenContext } from "../screen";
|
|
20
|
+
import { useScreenStateStore } from "../navigation/useScreenStateStore";
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* If onCellTap is defined execute the function and
|
|
@@ -42,10 +43,12 @@ export const useCellClick = ({
|
|
|
42
43
|
}: Props): onPressReturnFn => {
|
|
43
44
|
const { push, currentRoute } = useNavigation();
|
|
44
45
|
const { pathname } = useRoute();
|
|
46
|
+
const screenStateStore = useScreenStateStore();
|
|
45
47
|
|
|
46
48
|
const onCellTap: Option<Function> = React.useContext(CellTapContext);
|
|
47
49
|
const actionExecutor = React.useContext(ActionExecutorContext);
|
|
48
50
|
const screenData = useCurrentScreenData();
|
|
51
|
+
const screenState = useScreenContext()?.options;
|
|
49
52
|
|
|
50
53
|
const cellSelectable = toBooleanWithDefaultTrue(
|
|
51
54
|
component?.rules?.component_cells_selectable
|
|
@@ -83,6 +86,9 @@ export const useCellClick = ({
|
|
|
83
86
|
await actionExecutor?.handleEntryActions(selectedItem, {
|
|
84
87
|
component,
|
|
85
88
|
screenData,
|
|
89
|
+
screenState,
|
|
90
|
+
screenRoute: pathname,
|
|
91
|
+
screenStateStore,
|
|
86
92
|
});
|
|
87
93
|
}
|
|
88
94
|
|
|
@@ -117,6 +123,7 @@ export const useCellClick = ({
|
|
|
117
123
|
push,
|
|
118
124
|
sendAnalyticsOnPress,
|
|
119
125
|
screenData,
|
|
126
|
+
screenState,
|
|
120
127
|
]
|
|
121
128
|
);
|
|
122
129
|
|
|
@@ -138,6 +138,11 @@ describe("useFeedLoader", () => {
|
|
|
138
138
|
expect(loadPipesDataSpy).toBeCalledWith(feedUrl, {
|
|
139
139
|
clearCache: true,
|
|
140
140
|
riverId: undefined,
|
|
141
|
+
resolvers: {
|
|
142
|
+
screen: {
|
|
143
|
+
screenStateStore: undefined,
|
|
144
|
+
},
|
|
145
|
+
},
|
|
141
146
|
});
|
|
142
147
|
|
|
143
148
|
const store2 = mockStore({
|
|
@@ -179,6 +184,11 @@ describe("useFeedLoader", () => {
|
|
|
179
184
|
expect(loadPipesDataSpy).toBeCalledWith(feedUrl, {
|
|
180
185
|
clearCache: true,
|
|
181
186
|
riverId: undefined,
|
|
187
|
+
resolvers: {
|
|
188
|
+
screen: {
|
|
189
|
+
screenStateStore: undefined,
|
|
190
|
+
},
|
|
191
|
+
},
|
|
182
192
|
});
|
|
183
193
|
|
|
184
194
|
const store2 = mockStore({
|
|
@@ -228,6 +238,11 @@ describe("useFeedLoader", () => {
|
|
|
228
238
|
expect(loadPipesDataSpy).toBeCalledWith(feedUrl, {
|
|
229
239
|
clearCache: true,
|
|
230
240
|
silentRefresh: true,
|
|
241
|
+
resolvers: {
|
|
242
|
+
screen: {
|
|
243
|
+
screenStateStore: undefined,
|
|
244
|
+
},
|
|
245
|
+
},
|
|
231
246
|
});
|
|
232
247
|
|
|
233
248
|
loadPipesDataSpy.mockRestore();
|
|
@@ -267,6 +282,11 @@ describe("useFeedLoader", () => {
|
|
|
267
282
|
expect(loadPipesDataSpy).toBeCalledWith(nextUrl, {
|
|
268
283
|
parentFeed: feedUrlWithNext,
|
|
269
284
|
silentRefresh: true,
|
|
285
|
+
resolvers: {
|
|
286
|
+
screen: {
|
|
287
|
+
screenStateStore: undefined,
|
|
288
|
+
},
|
|
289
|
+
},
|
|
270
290
|
});
|
|
271
291
|
|
|
272
292
|
loadPipesDataSpy.mockRestore();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { complement, compose, isNil, map, min, prop, take, uniq } from "ramda";
|
|
2
2
|
import { useDispatch } from "react-redux";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { useZappPipesFeeds } from "@applicaster/zapp-react-native-redux/hooks
|
|
4
|
+
import { useZappPipesFeeds } from "@applicaster/zapp-react-native-redux/hooks";
|
|
5
5
|
import { loadPipesData } from "@applicaster/zapp-react-native-redux/ZappPipes";
|
|
6
6
|
import { isNilOrEmpty } from "../../reactUtils/helpers";
|
|
7
7
|
import { ZappPipesSearchContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
getInflatedDataSourceUrl,
|
|
10
10
|
getSearchContext,
|
|
11
11
|
} from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
12
|
+
import { isGallery } from "@applicaster/zapp-react-native-utils/componentsUtils";
|
|
12
13
|
import { useScreenContext } from "../screen/useScreenContext";
|
|
13
14
|
|
|
14
15
|
type Options = {
|
|
@@ -46,9 +47,7 @@ const filterEmptyData = (data) => {
|
|
|
46
47
|
};
|
|
47
48
|
|
|
48
49
|
const getData = (rawData) =>
|
|
49
|
-
rawData.
|
|
50
|
-
? rawData.ui_components[0].data
|
|
51
|
-
: rawData.data;
|
|
50
|
+
isGallery(rawData) ? rawData.ui_components[0].data : rawData.data;
|
|
52
51
|
|
|
53
52
|
const extractData = compose(uniq, map(getData));
|
|
54
53
|
|
|
@@ -70,13 +69,12 @@ export const useBatchLoading = (
|
|
|
70
69
|
const [hasEverBeenReady, setHasEverBeenReady] = React.useState(false);
|
|
71
70
|
|
|
72
71
|
// if first component is gallery-qb, take only one component for initial load
|
|
73
|
-
const takeSize =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
);
|
|
72
|
+
const takeSize = isGallery(componentsToRender?.[0])
|
|
73
|
+
? 1
|
|
74
|
+
: min(
|
|
75
|
+
options.initialBatchSize ?? DEFAULT_BATCH_SIZE,
|
|
76
|
+
componentsToRender.length
|
|
77
|
+
);
|
|
80
78
|
|
|
81
79
|
const takeBatchSize = React.useCallback(take(takeSize), [takeSize]);
|
|
82
80
|
|
|
@@ -146,11 +144,11 @@ export const useBatchLoading = (
|
|
|
146
144
|
}
|
|
147
145
|
}
|
|
148
146
|
});
|
|
149
|
-
}, [feedUrls]);
|
|
147
|
+
}, [feedUrls, feeds]);
|
|
150
148
|
|
|
151
149
|
React.useEffect(() => {
|
|
152
150
|
runBatchLoading();
|
|
153
|
-
}, []);
|
|
151
|
+
}, [runBatchLoading]); // Adding runBatchLoading as a dependency to ensure that it reloads feeds when clearPipesData is called
|
|
154
152
|
|
|
155
153
|
React.useEffect(() => {
|
|
156
154
|
// check if all feeds are ready and set hasEverBeenReady to true
|
|
@@ -8,6 +8,7 @@ import { reactHooksLogger } from "../logger";
|
|
|
8
8
|
import { shouldDispatchData, useIsInitialRender } from "../utils";
|
|
9
9
|
import { useInflatedUrl } from "./useInflatedUrl";
|
|
10
10
|
import { useRoute } from "../navigation";
|
|
11
|
+
import { useScreenResolvers } from "@applicaster/zapp-react-native-utils/actionsExecutor/screenResolver";
|
|
11
12
|
|
|
12
13
|
const logger = reactHooksLogger.addSubsystem("useFeedLoader");
|
|
13
14
|
|
|
@@ -51,6 +52,7 @@ export const useFeedLoader = ({
|
|
|
51
52
|
const isInitialRender = useIsInitialRender();
|
|
52
53
|
const dispatch = useDispatch();
|
|
53
54
|
const { screenData } = useRoute();
|
|
55
|
+
const resolvers = useScreenResolvers();
|
|
54
56
|
|
|
55
57
|
const callableFeedUrl = useInflatedUrl({ feedUrl, mapping });
|
|
56
58
|
|
|
@@ -69,11 +71,12 @@ export const useFeedLoader = ({
|
|
|
69
71
|
silentRefresh,
|
|
70
72
|
callback,
|
|
71
73
|
riverId,
|
|
74
|
+
resolvers,
|
|
72
75
|
})
|
|
73
76
|
);
|
|
74
77
|
}
|
|
75
78
|
},
|
|
76
|
-
[callableFeedUrl]
|
|
79
|
+
[callableFeedUrl, resolvers]
|
|
77
80
|
);
|
|
78
81
|
|
|
79
82
|
const loadNext: FeedLoaderResponse["loadNext"] = React.useCallback(() => {
|
|
@@ -86,11 +89,12 @@ export const useFeedLoader = ({
|
|
|
86
89
|
silentRefresh: true,
|
|
87
90
|
parentFeed: callableFeedUrl,
|
|
88
91
|
riverId,
|
|
92
|
+
resolvers,
|
|
89
93
|
})
|
|
90
94
|
);
|
|
91
95
|
}
|
|
92
96
|
}
|
|
93
|
-
}, [callableFeedUrl, currentFeed?.data?.next]);
|
|
97
|
+
}, [callableFeedUrl, currentFeed?.data?.next, resolvers]);
|
|
94
98
|
|
|
95
99
|
useEffect(() => {
|
|
96
100
|
if (
|
|
@@ -102,6 +106,7 @@ export const useFeedLoader = ({
|
|
|
102
106
|
...pipesOptions,
|
|
103
107
|
clearCache: true,
|
|
104
108
|
riverId,
|
|
109
|
+
resolvers,
|
|
105
110
|
})
|
|
106
111
|
);
|
|
107
112
|
} else if (!callableFeedUrl) {
|
|
@@ -126,14 +131,16 @@ export const useFeedLoader = ({
|
|
|
126
131
|
jsOnly: true,
|
|
127
132
|
});
|
|
128
133
|
}
|
|
129
|
-
}, []);
|
|
134
|
+
}, [resolvers]);
|
|
130
135
|
|
|
131
136
|
// Reload feed when feedUrl changes, unless skipLoading is true
|
|
132
137
|
useEffect(() => {
|
|
133
138
|
if (!isInitialRender && callableFeedUrl && !pipesOptions.skipLoading) {
|
|
134
|
-
dispatch(
|
|
139
|
+
dispatch(
|
|
140
|
+
loadPipesData(callableFeedUrl, { ...pipesOptions, riverId, resolvers })
|
|
141
|
+
);
|
|
135
142
|
}
|
|
136
|
-
}, [callableFeedUrl]);
|
|
143
|
+
}, [callableFeedUrl, resolvers]);
|
|
137
144
|
|
|
138
145
|
return React.useMemo(() => {
|
|
139
146
|
if (!callableFeedUrl || !feedUrl) {
|
|
@@ -3,7 +3,7 @@ import { platformSelect } from "../../reactUtils";
|
|
|
3
3
|
const TAB_BAR_HEIGHT_IOS = 49;
|
|
4
4
|
const TAB_BAR_HEIGHT_ANDROID = 56;
|
|
5
5
|
|
|
6
|
-
export const
|
|
6
|
+
export const getTabBarHeight = () =>
|
|
7
7
|
platformSelect({
|
|
8
8
|
ios: TAB_BAR_HEIGHT_IOS,
|
|
9
9
|
android: TAB_BAR_HEIGHT_ANDROID,
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { useGetNavBarTopBorderWidth } from "./useGetNavBarTopBorderWidth";
|
|
2
|
-
import {
|
|
2
|
+
import { getTabBarHeight } from "./getTabBarHeight";
|
|
3
|
+
import { useNavigation } from "./useNavigation";
|
|
3
4
|
import { MenuTypes, useNavigationType } from "./useNavigationType";
|
|
5
|
+
import { useNavigationPluginData } from "./useNavigationPluginData";
|
|
4
6
|
|
|
5
7
|
export const useGetBottomTabBarHeight = (): number => {
|
|
8
|
+
const { activeRiver } = useNavigation();
|
|
9
|
+
|
|
10
|
+
const navigationPluginData = useNavigationPluginData(activeRiver);
|
|
11
|
+
const navigationType = useNavigationType(navigationPluginData);
|
|
12
|
+
|
|
6
13
|
const topBorderWidth = useGetNavBarTopBorderWidth();
|
|
7
|
-
const tabBarHeight =
|
|
14
|
+
const tabBarHeight = getTabBarHeight();
|
|
8
15
|
|
|
9
|
-
const isBottomBarNavigation =
|
|
16
|
+
const isBottomBarNavigation = navigationType === MenuTypes.bottomTabBar;
|
|
10
17
|
|
|
11
18
|
return !isBottomBarNavigation ? 0 : tabBarHeight + topBorderWidth;
|
|
12
19
|
};
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { useRoute } from "./useRoute";
|
|
2
2
|
|
|
3
|
-
export const useNavigationPluginData = (
|
|
3
|
+
export const useNavigationPluginData = (
|
|
4
|
+
screenData?: LegacyNavigationScreenData | null
|
|
5
|
+
): ZappNavigation | undefined => {
|
|
4
6
|
const {
|
|
5
7
|
screenData: useRouteScreenData,
|
|
6
8
|
}: { screenData: QuickBrickNavigationData | null } = useRoute();
|
|
7
9
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
const activeScreenData = screenData ?? useRouteScreenData;
|
|
11
|
+
|
|
12
|
+
const navigations = activeScreenData?.targetScreen
|
|
13
|
+
? (activeScreenData.targetScreen as ZappRiver).navigations
|
|
14
|
+
: (activeScreenData as ZappRiver).navigations;
|
|
11
15
|
|
|
12
16
|
const navigationMenu = navigations?.find((nav) => nav.category === "menu");
|
|
13
17
|
|
|
@@ -5,8 +5,10 @@ export enum MenuTypes {
|
|
|
5
5
|
bottomTabBar = "BOTTOM_TAB_BAR",
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
export const useNavigationType = (): MenuTypes => {
|
|
9
|
-
const
|
|
8
|
+
export const useNavigationType = (navigation?: ZappNavigation): MenuTypes => {
|
|
9
|
+
const navigationPluginData = useNavigationPluginData();
|
|
10
|
+
|
|
11
|
+
const navigationMenu = navigation ?? navigationPluginData;
|
|
10
12
|
|
|
11
13
|
return !navigationMenu ||
|
|
12
14
|
navigationMenu.navigation_type === "quick_brick_side_menu"
|
|
@@ -28,14 +28,19 @@ const isHookPathname = (pathname: string) => /^\/hooks\//.test(pathname);
|
|
|
28
28
|
|
|
29
29
|
type VariousScreenData = LegacyNavigationScreenData | ZappRiver | ZappEntry;
|
|
30
30
|
|
|
31
|
-
export const useRoute = (
|
|
31
|
+
export const useRoute = (
|
|
32
|
+
useLegacy = true
|
|
33
|
+
): {
|
|
32
34
|
screenData: VariousScreenData;
|
|
33
35
|
pathname: string;
|
|
34
36
|
} => {
|
|
35
37
|
const pathname = usePathname() || "";
|
|
36
38
|
const navigator = useNavigation();
|
|
39
|
+
const screenContext = useContext(ScreenDataContext);
|
|
37
40
|
|
|
38
|
-
const screenDataContext =
|
|
41
|
+
const screenDataContext = useLegacy
|
|
42
|
+
? legacyScreenData(screenContext)
|
|
43
|
+
: screenContext;
|
|
39
44
|
|
|
40
45
|
const { plugins, contentTypes, rivers } = usePickFromState([
|
|
41
46
|
"plugins",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useRoute } from "./useRoute";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
|
|
4
|
+
export const useScreenStateStore = () => {
|
|
5
|
+
const route = useRoute(false);
|
|
6
|
+
|
|
7
|
+
return useMemo(
|
|
8
|
+
() => route.screenData?.screenStateStore,
|
|
9
|
+
[route.screenData?.screenStateStore]
|
|
10
|
+
);
|
|
11
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { renderHook } from "@testing-library/react-hooks";
|
|
2
|
+
|
|
3
|
+
jest.mock(
|
|
4
|
+
"@applicaster/zapp-react-native-ui-components/Components/River/useScreenConfiguration",
|
|
5
|
+
() => ({
|
|
6
|
+
useScreenConfiguration: jest.fn(),
|
|
7
|
+
})
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
useScreenConfiguration,
|
|
12
|
+
} = require("@applicaster/zapp-react-native-ui-components/Components/River/useScreenConfiguration");
|
|
13
|
+
|
|
14
|
+
const { useScreenBackgroundColor } = require("../useScreenBackgroundColor");
|
|
15
|
+
|
|
16
|
+
describe("useScreenBackgroundColor", () => {
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
jest.clearAllMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should return the background color from screen configuration", () => {
|
|
22
|
+
useScreenConfiguration.mockReturnValue({
|
|
23
|
+
backgroundColor: "#FF0000",
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Render the hook with a screen ID
|
|
27
|
+
const { result } = renderHook(() =>
|
|
28
|
+
useScreenBackgroundColor("test-screen-id")
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
expect(result.current).toBe("#FF0000");
|
|
32
|
+
|
|
33
|
+
expect(useScreenConfiguration).toHaveBeenCalledWith("test-screen-id");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should return 'transparent' when background color is empty", () => {
|
|
37
|
+
useScreenConfiguration.mockReturnValue({
|
|
38
|
+
backgroundColor: "",
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const { result } = renderHook(() =>
|
|
42
|
+
useScreenBackgroundColor("test-screen-id")
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
expect(result.current).toBe("transparent");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should return 'transparent' when background color is null", () => {
|
|
49
|
+
useScreenConfiguration.mockReturnValue({
|
|
50
|
+
backgroundColor: null,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const { result } = renderHook(() =>
|
|
54
|
+
useScreenBackgroundColor("test-screen-id")
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
expect(result.current).toBe("transparent");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("should return 'transparent' when background color is undefined", () => {
|
|
61
|
+
useScreenConfiguration.mockReturnValue({});
|
|
62
|
+
|
|
63
|
+
const { result } = renderHook(() =>
|
|
64
|
+
useScreenBackgroundColor("test-screen-id")
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(result.current).toBe("transparent");
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -1,23 +1,11 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
3
1
|
import { useScreenConfiguration } from "@applicaster/zapp-react-native-ui-components/Components/River/useScreenConfiguration";
|
|
4
2
|
import { ifEmptyUseFallback } from "@applicaster/zapp-react-native-utils/cellUtils";
|
|
5
3
|
|
|
4
|
+
const DEFAULT_BACKGROUND_FALLBACK = "transparent";
|
|
5
|
+
|
|
6
6
|
export const useScreenBackgroundColor = (screenId: string): string => {
|
|
7
7
|
const { backgroundColor: screenBackgroundColor } =
|
|
8
8
|
useScreenConfiguration(screenId);
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const themeBackgroundColor = React.useMemo(
|
|
13
|
-
() => theme.app_background_color,
|
|
14
|
-
[theme.app_background_color]
|
|
15
|
-
);
|
|
16
|
-
|
|
17
|
-
const backgroundColor = ifEmptyUseFallback(
|
|
18
|
-
screenBackgroundColor,
|
|
19
|
-
themeBackgroundColor
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
return backgroundColor;
|
|
10
|
+
return ifEmptyUseFallback(screenBackgroundColor, DEFAULT_BACKGROUND_FALLBACK);
|
|
23
11
|
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# ZStoreProvider and useZStore
|
|
2
|
+
|
|
3
|
+
This module provides a React context-based solution for managing Zustand stores with named access.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
### ZStoreProvider
|
|
8
|
+
|
|
9
|
+
The `ZStoreProvider` component creates a Zustand store from the provided value and makes it available to child components.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { ZStoreProvider } from "@applicaster/zapp-react-native-utils/reactHooks/state";
|
|
13
|
+
|
|
14
|
+
// In your component
|
|
15
|
+
<ZStoreProvider name="playerConfiguration" value={controller?.config}>
|
|
16
|
+
<YourComponent />
|
|
17
|
+
</ZStoreProvider>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### useZStore
|
|
21
|
+
|
|
22
|
+
The `useZStore` hook allows you to access a Zustand store by name from within a `ZStoreProvider`.
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { useZStore } from "@applicaster/zapp-react-native-utils/reactHooks/state";
|
|
26
|
+
import { useStore } from "zustand";
|
|
27
|
+
|
|
28
|
+
// In your component
|
|
29
|
+
const MyComponent = () => {
|
|
30
|
+
const store = useZStore("playerConfiguration");
|
|
31
|
+
const config = useStore(store, (state) => state.someProperty);
|
|
32
|
+
|
|
33
|
+
return <Text>{config}</Text>;
|
|
34
|
+
};
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Example
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import React from "react";
|
|
41
|
+
import { ZStoreProvider, useZStore } from "@applicaster/zapp-react-native-utils/reactHooks/state";
|
|
42
|
+
import { useStore } from "zustand";
|
|
43
|
+
|
|
44
|
+
// Component that uses the store
|
|
45
|
+
const PlayerConfigDisplay = () => {
|
|
46
|
+
const store = useZStore("playerConfiguration");
|
|
47
|
+
const config = useStore(store, (state) => state);
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<View>
|
|
51
|
+
<Text>Player Config: {JSON.stringify(config)}</Text>
|
|
52
|
+
</View>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Main component that provides the store
|
|
57
|
+
const PlayerComponent = ({ controller }) => {
|
|
58
|
+
return (
|
|
59
|
+
<ZStoreProvider name="playerConfiguration" value={controller?.config}>
|
|
60
|
+
<PlayerConfigDisplay />
|
|
61
|
+
</ZStoreProvider>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Features
|
|
67
|
+
|
|
68
|
+
- **Named Stores**: Access stores by name instead of importing them directly
|
|
69
|
+
- **Context-based**: Uses React Context for store management
|
|
70
|
+
- **Zustand Integration**: Seamlessly works with existing Zustand stores
|
|
71
|
+
- **Type Safety**: Full TypeScript support
|
|
72
|
+
- **Error Handling**: Clear error messages when stores are not found or used outside providers
|
|
73
|
+
|
|
74
|
+
## Error Handling
|
|
75
|
+
|
|
76
|
+
The `useZStore` hook will throw errors in the following cases:
|
|
77
|
+
|
|
78
|
+
1. **Used outside provider**: "useZStore must be used within a ZStoreProvider"
|
|
79
|
+
2. **Store not found**: "Store with name 'storeName' not found. Make sure it's provided by a ZStoreProvider"
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React, { createContext, useContext, ReactNode, useMemo } from "react";
|
|
2
|
+
import { create, UseBoundStore } from "zustand";
|
|
3
|
+
|
|
4
|
+
interface ZStoreContextType {
|
|
5
|
+
stores: Map<string, UseBoundStore<any>>;
|
|
6
|
+
registerStore: (name: string, store: UseBoundStore<any>) => void;
|
|
7
|
+
getStore: (name: string) => UseBoundStore<any> | undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const ZStoreContext = createContext<ZStoreContextType | null>(null);
|
|
11
|
+
|
|
12
|
+
interface ZStoreProviderProps {
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
name: string;
|
|
15
|
+
value: any;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const ZStoreProvider: React.FC<ZStoreProviderProps> = ({
|
|
19
|
+
children,
|
|
20
|
+
name,
|
|
21
|
+
value,
|
|
22
|
+
}) => {
|
|
23
|
+
const parentContext = useContext(ZStoreContext);
|
|
24
|
+
|
|
25
|
+
const context = useMemo(() => {
|
|
26
|
+
if (parentContext) {
|
|
27
|
+
// If parent context exists, create a new store and register it
|
|
28
|
+
const store = create(() => value);
|
|
29
|
+
parentContext.registerStore(name, store);
|
|
30
|
+
|
|
31
|
+
return parentContext;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Create a new context if none exists
|
|
35
|
+
const stores = new Map<string, UseBoundStore<any>>();
|
|
36
|
+
|
|
37
|
+
// Create a store from the provided value
|
|
38
|
+
const store = create(() => value);
|
|
39
|
+
stores.set(name, store);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
stores,
|
|
43
|
+
registerStore: (storeName: string, storeInstance: UseBoundStore<any>) => {
|
|
44
|
+
stores.set(storeName, storeInstance);
|
|
45
|
+
},
|
|
46
|
+
getStore: (storeName: string) => stores.get(storeName),
|
|
47
|
+
};
|
|
48
|
+
}, [parentContext, name, value]);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<ZStoreContext.Provider value={context}>{children}</ZStoreContext.Provider>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const useZStore = (name: string): UseBoundStore<any> => {
|
|
56
|
+
const context = useContext(ZStoreContext);
|
|
57
|
+
|
|
58
|
+
if (!context) {
|
|
59
|
+
throw new Error("useZStore must be used within a ZStoreProvider");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const store = context.getStore(name);
|
|
63
|
+
|
|
64
|
+
if (!store) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Store with name "${name}" not found. Make sure it's provided by a ZStoreProvider.`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return store;
|
|
71
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react-native";
|
|
3
|
+
import { Text } from "react-native";
|
|
4
|
+
import { ZStoreProvider, useZStore } from "../ZStoreProvider";
|
|
5
|
+
import { useStore } from "zustand";
|
|
6
|
+
|
|
7
|
+
interface TestState {
|
|
8
|
+
value: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Test component that uses the store
|
|
12
|
+
const TestComponent = ({ storeName }: { storeName: string }) => {
|
|
13
|
+
const store = useZStore(storeName);
|
|
14
|
+
const value = useStore(store, (state: any) => (state as TestState).value);
|
|
15
|
+
|
|
16
|
+
return <Text testID="test-value">{value}</Text>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Test component that provides a store
|
|
20
|
+
const TestProvider = ({ children }: { children: React.ReactNode }) => {
|
|
21
|
+
return (
|
|
22
|
+
<ZStoreProvider name="testStore" value={{ value: "test-value" }}>
|
|
23
|
+
{children}
|
|
24
|
+
</ZStoreProvider>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
describe("ZStoreProvider and useZStore", () => {
|
|
29
|
+
it("should provide a store and allow access via useZStore", () => {
|
|
30
|
+
render(
|
|
31
|
+
<TestProvider>
|
|
32
|
+
<TestComponent storeName="testStore" />
|
|
33
|
+
</TestProvider>
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
expect(screen.getByTestId("test-value").props.children).toBe("test-value");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should throw error when useZStore is used outside provider", () => {
|
|
40
|
+
// Suppress console.error for this test
|
|
41
|
+
const originalError = console.error;
|
|
42
|
+
console.error = jest.fn();
|
|
43
|
+
|
|
44
|
+
expect(() => {
|
|
45
|
+
render(<TestComponent storeName="testStore" />);
|
|
46
|
+
}).toThrow("useZStore must be used within a ZStoreProvider");
|
|
47
|
+
|
|
48
|
+
console.error = originalError;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should throw error when store name is not found", () => {
|
|
52
|
+
// Suppress console.error for this test
|
|
53
|
+
const originalError = console.error;
|
|
54
|
+
console.error = jest.fn();
|
|
55
|
+
|
|
56
|
+
expect(() => {
|
|
57
|
+
render(
|
|
58
|
+
<TestProvider>
|
|
59
|
+
<TestComponent storeName="nonExistentStore" />
|
|
60
|
+
</TestProvider>
|
|
61
|
+
);
|
|
62
|
+
}).toThrow('Store with name "nonExistentStore" not found');
|
|
63
|
+
|
|
64
|
+
console.error = originalError;
|
|
65
|
+
});
|
|
66
|
+
});
|
package/reactUtils/index.ts
CHANGED
|
@@ -187,3 +187,12 @@ export const hasVizioAPIs = () => {
|
|
|
187
187
|
|
|
188
188
|
return hasAPIs;
|
|
189
189
|
};
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Checks if the Android version is at least the expected version
|
|
193
|
+
* @param expectedVersion The version to compare against
|
|
194
|
+
* @returns True if the current Android version is at least the expected version, false otherwise
|
|
195
|
+
*/
|
|
196
|
+
export const isAndroidVersionAtLeast = (expectedVersion: number) => {
|
|
197
|
+
return parseFloat(Platform.Version.toString()) >= expectedVersion;
|
|
198
|
+
};
|