@applicaster/zapp-react-native-utils 15.0.0-rc.98 → 15.1.0-alpha.1852552014

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 (172) hide show
  1. package/actionsExecutor/ActionExecutorContext.tsx +51 -5
  2. package/actionsExecutor/feedDecorator.ts +6 -6
  3. package/adsUtils/index.ts +2 -2
  4. package/analyticsUtils/AnalyticsEvents/sendMenuClickEvent.ts +2 -2
  5. package/analyticsUtils/PlayerAnalyticsManager.ts +11 -1
  6. package/analyticsUtils/README.md +1 -1
  7. package/analyticsUtils/__tests__/analyticsMapper.test.ts +35 -0
  8. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testACP_events.json +1 -1
  9. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testBlockUnlistedParams_rules.json +1 -1
  10. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testEmptyRenameKeepsName_events.json +4 -0
  11. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testEmptyRenameKeepsName_rules.json +6 -0
  12. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testEventRegex_events.json +3 -9
  13. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testIgnoreOrdering_events.json +18 -0
  14. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testIgnoreOrdering_rules.json +16 -0
  15. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexEventNoFallback_events.json +4 -0
  16. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexEventNoFallback_rules.json +6 -0
  17. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexMultiGroupRename_events.json +4 -0
  18. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexMultiGroupRename_rules.json +6 -0
  19. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexNonNamedFullMatch_events.json +4 -0
  20. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexNonNamedFullMatch_rules.json +6 -0
  21. package/analyticsUtils/__tests__/fixtures/index.js +20 -0
  22. package/analyticsUtils/analyticsMapper.ts +4 -1
  23. package/analyticsUtils/playerAnalyticsTracker.ts +26 -3
  24. package/appUtils/HooksManager/__tests__/__snapshots__/hooksManager.test.js.snap +188 -0
  25. package/appUtils/HooksManager/__tests__/hooksManager.test.js +2 -16
  26. package/appUtils/HooksManager/index.ts +47 -17
  27. package/appUtils/RiverFocusManager/{index.ts → index.js} +18 -25
  28. package/appUtils/accessibilityManager/const.ts +0 -4
  29. package/appUtils/accessibilityManager/utils.ts +1 -0
  30. package/appUtils/contextKeysManager/__tests__/getKeys/failure.test.ts +2 -7
  31. package/appUtils/contextKeysManager/__tests__/getKeys/success.test.ts +0 -48
  32. package/appUtils/contextKeysManager/contextResolver.ts +22 -51
  33. package/appUtils/contextKeysManager/index.ts +10 -65
  34. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +0 -3
  35. package/appUtils/focusManager/index.ios.ts +4 -43
  36. package/appUtils/focusManagerAux/utils/index.ts +3 -5
  37. package/appUtils/focusManagerAux/utils/utils.ios.ts +3 -199
  38. package/appUtils/keyCodes/keys/keys.web.ts +4 -1
  39. package/appUtils/localizationsHelper.ts +4 -0
  40. package/appUtils/orientationHelper.ts +4 -2
  41. package/appUtils/platform/platformUtils.ts +1 -1
  42. package/appUtils/playerManager/__tests__/playerFactory.test.ts +150 -0
  43. package/appUtils/playerManager/index.ts +9 -0
  44. package/appUtils/playerManager/player.ts +1 -5
  45. package/appUtils/playerManager/playerFactory.ts +17 -34
  46. package/appUtils/playerManager/playerNative.ts +22 -30
  47. package/appUtils/playerManager/usePlayer.tsx +5 -3
  48. package/appUtils/playerManager/usePlayerState.tsx +2 -14
  49. package/cellUtils/__tests__/cellUtils.test.ts +39 -0
  50. package/cellUtils/index.ts +14 -6
  51. package/cloudEventsUtils/index.ts +1 -65
  52. package/colorUtils/__tests__/isTransparentColor.test.ts +76 -0
  53. package/colorUtils/__tests__/isValidColor.test.ts +70 -0
  54. package/colorUtils/index.ts +50 -0
  55. package/configurationUtils/__tests__/manifestKeyParser.test.ts +26 -26
  56. package/dateUtils/__tests__/dayjs.test.ts +0 -3
  57. package/dateUtils/index.ts +2 -0
  58. package/focusManager/aux/index.ts +1 -1
  59. package/headersUtils/__tests__/headersUtils.test.js +1 -11
  60. package/headersUtils/index.ts +1 -2
  61. package/manifestUtils/defaultManifestConfigurations/player.js +7 -75
  62. package/manifestUtils/keys.js +1 -0
  63. package/manifestUtils/platformIsTV.js +14 -0
  64. package/manifestUtils/tvAction/container/index.js +1 -1
  65. package/numberUtils/__tests__/toNumber.test.ts +0 -12
  66. package/numberUtils/index.ts +1 -19
  67. package/package.json +4 -4
  68. package/pipesUtils/__tests__/buildUrlWithQuery.test.ts +33 -0
  69. package/pipesUtils/__tests__/withPipesEndpoint.test.tsx +56 -0
  70. package/pipesUtils/index.ts +1 -0
  71. package/pipesUtils/withPipesEndpoint.tsx +54 -0
  72. package/playerUtils/index.ts +24 -2
  73. package/pluginUtils/index.ts +4 -9
  74. package/reactHooks/advertising/index.ts +2 -2
  75. package/reactHooks/app/__tests__/useAppState.test.ts +1 -1
  76. package/reactHooks/autoscrolling/__tests__/useTrackCurrentAutoScrollingElement.test.ts +1 -1
  77. package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +2 -1
  78. package/reactHooks/casting/index.ts +1 -0
  79. package/reactHooks/casting/useIsCasting.tsx +57 -0
  80. package/reactHooks/cell-click/__tests__/index.test.js +3 -1
  81. package/reactHooks/componentsMap/index.ts +55 -0
  82. package/reactHooks/configuration/__tests__/index.test.tsx +1 -1
  83. package/reactHooks/connection/__tests__/index.test.js +1 -1
  84. package/reactHooks/debugging/__tests__/index.test.js +4 -4
  85. package/reactHooks/device/useIsTablet.tsx +19 -14
  86. package/reactHooks/device/useMemoizedIsTablet.ts +3 -3
  87. package/reactHooks/events/index.ts +20 -0
  88. package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +23 -32
  89. package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +19 -19
  90. package/reactHooks/feed/__tests__/useEntryScreenId.test.tsx +1 -4
  91. package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +30 -42
  92. package/reactHooks/feed/__tests__/useFeedRefresh.test.tsx +1 -1
  93. package/reactHooks/feed/__tests__/useInflatedUrl.test.tsx +1 -1
  94. package/reactHooks/feed/useBatchLoading.ts +1 -7
  95. package/reactHooks/feed/useEntryScreenId.ts +2 -2
  96. package/reactHooks/feed/useInflatedUrl.ts +1 -1
  97. package/reactHooks/feed/usePipesCacheReset.ts +1 -3
  98. package/reactHooks/flatList/useLoadNextPageIfNeeded.ts +16 -13
  99. package/reactHooks/index.ts +2 -0
  100. package/reactHooks/layout/__tests__/index.test.tsx +1 -1
  101. package/reactHooks/layout/__tests__/useLayoutVersion.test.tsx +1 -1
  102. package/reactHooks/layout/useDimensions/__tests__/{useDimensions.test.tsx → useDimensions.test.ts} +25 -105
  103. package/reactHooks/layout/useDimensions/useDimensions.ts +2 -2
  104. package/reactHooks/navigation/__tests__/index.test.tsx +4 -2
  105. package/reactHooks/navigation/index.ts +6 -7
  106. package/reactHooks/navigation/useRoute.ts +6 -8
  107. package/reactHooks/player/TVSeekControlller/TVSeekController.ts +10 -27
  108. package/reactHooks/player/__tests__/useAutoSeek._test.tsx +1 -1
  109. package/reactHooks/player/__tests__/useTapSeek._test.ts +1 -1
  110. package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +1 -1
  111. package/reactHooks/resolvers/__tests__/useComponentResolver.test.tsx +1 -1
  112. package/reactHooks/resolvers/useCellResolver.ts +2 -6
  113. package/reactHooks/resolvers/useComponentResolver.ts +13 -9
  114. package/reactHooks/screen/__tests__/useCurrentScreenData.test.tsx +2 -2
  115. package/reactHooks/screen/__tests__/useCurrentScreenIsHook.test.ts +103 -0
  116. package/reactHooks/screen/__tests__/useCurrentScreenIsStartupHook.test.ts +94 -0
  117. package/reactHooks/screen/__tests__/useScreenBackgroundColor.test.tsx +1 -1
  118. package/reactHooks/screen/__tests__/useScreenData.test.tsx +1 -1
  119. package/reactHooks/screen/__tests__/useTargetScreenData.test.tsx +4 -12
  120. package/reactHooks/screen/index.ts +6 -0
  121. package/reactHooks/screen/useCurrentScreenIsHook.ts +9 -0
  122. package/reactHooks/screen/useCurrentScreenIsStartupHook.ts +8 -0
  123. package/reactHooks/screen/useIsStandaloneFullscreen.ts +12 -0
  124. package/reactHooks/screen/useTargetScreenData.ts +2 -4
  125. package/reactHooks/state/__tests__/useComponentScreenState.test.ts +246 -0
  126. package/reactHooks/state/index.ts +2 -0
  127. package/reactHooks/state/useComponentScreenState.ts +45 -0
  128. package/reactHooks/state/useRivers.ts +1 -1
  129. package/reactHooks/usePluginConfiguration.ts +2 -2
  130. package/reactHooks/utils/__tests__/index.test.js +1 -1
  131. package/reactHooks/videoModal/hooks/useVideoModalScreenData.tsx +22 -4
  132. package/rectUtils/index.ts +2 -2
  133. package/riverComponetsMeasurementProvider/index.tsx +12 -8
  134. package/screenPickerUtils/index.ts +5 -0
  135. package/screenState/__tests__/index.test.ts +1 -1
  136. package/stringUtils/index.ts +1 -1
  137. package/testUtils/index.tsx +21 -30
  138. package/typeGuards/index.ts +0 -4
  139. package/utils/__tests__/clone.test.ts +158 -0
  140. package/utils/clone.ts +7 -0
  141. package/utils/index.ts +1 -13
  142. package/utils/path.ts +3 -6
  143. package/utils/pathOr.ts +1 -5
  144. package/zappFrameworkUtils/HookCallback/callbackNavigationAction.ts +2 -2
  145. package/zappFrameworkUtils/HookCallback/hookCallbackManifestExtensions.config.js +1 -1
  146. package/zappFrameworkUtils/loginPluginUtils.ts +1 -1
  147. package/adsUtils/__tests__/createVMAP.test.ts +0 -419
  148. package/appDataUtils/__tests__/urlScheme.test.ts +0 -678
  149. package/appUtils/focusManagerAux/utils/index.ios.ts +0 -122
  150. package/cloudEventsUtils/__tests__/index.test.ts +0 -529
  151. package/enumUtils/__tests__/getEnumKeyByEnumValue.test.ts +0 -207
  152. package/errorUtils/__tests__/GeneralError.test.ts +0 -97
  153. package/errorUtils/__tests__/HttpStatusCode.test.ts +0 -344
  154. package/errorUtils/__tests__/MissingPluginError.test.ts +0 -113
  155. package/errorUtils/__tests__/NetworkError.test.ts +0 -202
  156. package/errorUtils/__tests__/getParsedResponse.test.ts +0 -188
  157. package/errorUtils/__tests__/invariant.test.ts +0 -112
  158. package/numberUtils/__tests__/toPositiveNumber.test.ts +0 -165
  159. package/reactHooks/analytics/__tests__/useSendAnalyticsOnPress.test.ts +0 -537
  160. package/reactHooks/dev/__tests__/useReRenderLog.test.ts +0 -188
  161. package/reactHooks/ui/__tests__/useFadeOutWhenBlurred.test.ts +0 -580
  162. package/rectUtils/__tests__/index.test.ts +0 -549
  163. package/screenPickerUtils/__tests__/index.test.ts +0 -333
  164. package/searchUtils/const.ts +0 -7
  165. package/searchUtils/index.ts +0 -3
  166. package/time/__tests__/BackgroundTimer.test.ts +0 -156
  167. package/time/__tests__/Timer.test.ts +0 -236
  168. package/typeGuards/__tests__/isString.test.ts +0 -21
  169. package/utils/__tests__/mergeRight.test.ts +0 -48
  170. package/utils/__tests__/selectors.test.ts +0 -124
  171. package/utils/mergeRight.ts +0 -5
  172. package/utils/selectors.ts +0 -46
@@ -0,0 +1,54 @@
1
+ import React, { forwardRef, RefObject } from "react";
2
+ import URL from "url";
3
+
4
+ import { useBuildPipesUrl } from "@applicaster/zapp-react-native-utils/reactHooks/feed";
5
+
6
+ /**
7
+ * Merges the query params resolved from a pipes endpoint context into the url.
8
+ * Returns the url unchanged when there is nothing to merge.
9
+ */
10
+ export function buildUrlWithQuery(url: string, requestParams): string {
11
+ if (!url || !requestParams?.params) {
12
+ return url;
13
+ }
14
+
15
+ const parsedURL = URL.parse(url, true);
16
+
17
+ parsedURL.query = { ...parsedURL.query, ...requestParams.params };
18
+ parsedURL.search = null;
19
+
20
+ return URL.format(parsedURL);
21
+ }
22
+
23
+ type Props = {
24
+ uri: string;
25
+ } & Record<string, unknown>;
26
+
27
+ /**
28
+ * HOC that resolves the wrapped component's `uri` against its matching Zapp
29
+ * Pipes endpoint. Endpoint context keys are added as query params (merged into
30
+ * the uri) and as request `headers`. Rendering is deferred until the context
31
+ * has been resolved so the component never loads without its required headers.
32
+ */
33
+ export function withPipesEndpoint(Component) {
34
+ function WithPipesEndpoint(props: Props, ref: RefObject<unknown>) {
35
+ const { requestParams } = useBuildPipesUrl({ url: props.uri });
36
+
37
+ if (requestParams !== null) {
38
+ const urlWithQuery = buildUrlWithQuery(props.uri, requestParams);
39
+
40
+ return (
41
+ <Component
42
+ ref={ref}
43
+ {...props}
44
+ uri={urlWithQuery}
45
+ headers={requestParams.headers || {}}
46
+ />
47
+ );
48
+ }
49
+
50
+ return null;
51
+ }
52
+
53
+ return forwardRef(WithPipesEndpoint);
54
+ }
@@ -9,6 +9,16 @@ import { Dimensions } from "react-native";
9
9
 
10
10
  export { getPlayerActionButtons } from "./getPlayerActionButtons";
11
11
 
12
+ export const HLS_MIME_TYPES = [
13
+ "application/x-mpegURL",
14
+ "application/vnd.apple.mpegurl",
15
+ ];
16
+
17
+ export const STREAMING_VIDEO_MIME_TYPES = [
18
+ ...HLS_MIME_TYPES,
19
+ "application/dash+xml",
20
+ ];
21
+
12
22
  /**
13
23
  * Gets duration value from player manager, and from extensions
14
24
  * then checks whether the value from either is a not a valid number
@@ -27,7 +37,7 @@ export function isLiveLegacy(content) {
27
37
  ]);
28
38
 
29
39
  const durationFromExt = R.path(["extensions", "duration"], content);
30
- const durationFromMgr = playerManager.getDuration();
40
+ const durationFromMgr = playerManager.getInstanceController()?.getDuration();
31
41
  const duration = Math.floor(durationFromExt || durationFromMgr);
32
42
 
33
43
  const isLive = R.anyPass([isNotaValidNumber, R.lte(R.__, 0)])(duration);
@@ -71,7 +81,9 @@ function isLiveByManager(): boolean {
71
81
  return true;
72
82
  }
73
83
 
74
- const durationFromPlayerManager = playerManager.getDuration();
84
+ const durationFromPlayerManager = playerManager
85
+ .getInstanceController()
86
+ ?.getDuration();
75
87
 
76
88
  return isLiveByDuration(durationFromPlayerManager);
77
89
  }
@@ -80,6 +92,16 @@ export function isLive(entry: ZappEntry): boolean {
80
92
  return isEntryLive(entry) || isLiveByManager();
81
93
  }
82
94
 
95
+ export const isVideoItem = (item: Option<ZappEntry>) => {
96
+ const contentType = item?.content?.type;
97
+
98
+ return (
99
+ isString(contentType) &&
100
+ (contentType.includes("video") ||
101
+ STREAMING_VIDEO_MIME_TYPES.includes(contentType))
102
+ );
103
+ };
104
+
83
105
  export const isAudioItem = (item: Option<ZappEntry>) => {
84
106
  if (
85
107
  isString(item?.content?.type) &&
@@ -6,7 +6,6 @@ import { deprecationMessage } from "../appUtils";
6
6
 
7
7
  import { pluginUtilsLogger } from "./logger";
8
8
  import { platformSelect } from "../reactUtils";
9
- import { get } from "../utils";
10
9
 
11
10
  type PluginModule = any;
12
11
  type Plugin = {
@@ -30,10 +29,7 @@ export function getComponentModule({
30
29
  components: ComponentsMap;
31
30
  plugins: Plugin[] | QuickBrickPlugin[];
32
31
  }) {
33
- const component = R.compose(
34
- R.prop(R.__, components),
35
- toPascalCase
36
- )(componentType);
32
+ const component = components[toPascalCase(componentType)];
37
33
 
38
34
  if (component) {
39
35
  return component;
@@ -60,7 +56,9 @@ export function findComponentByType({
60
56
  ? R.compose(...R.reverse(decorators))
61
57
  : decorators;
62
58
 
63
- const component = getComponentModule({ componentType, components, plugins });
59
+ const module = getComponentModule({ componentType, components, plugins });
60
+
61
+ const component = module?.Component || module;
64
62
 
65
63
  return R.unless(R.isNil, applyDecorators)(component);
66
64
  }
@@ -255,6 +253,3 @@ const getPluginType = R.pathOr("unknown_plugin_type", [
255
253
 
256
254
  export const isPlayerPlugin = (routeState): boolean =>
257
255
  getPluginType(routeState) === "player";
258
-
259
- export const getPluginModuleUrlScheme = (plugin) =>
260
- get(plugin, ["module", "urlScheme"]);
@@ -3,7 +3,7 @@ import { View } from "react-native";
3
3
  import * as R from "ramda";
4
4
 
5
5
  import { useNavigation } from "../navigation";
6
- import { usePlugins } from "@applicaster/zapp-react-native-redux/hooks";
6
+ import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
7
7
  import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
8
8
  import { dismissModal, openModal } from "../../modalState";
9
9
 
@@ -16,7 +16,7 @@ const ModalContainer = platformSelect({
16
16
 
17
17
  export function useAdvertisingInterstitial() {
18
18
  const { screenData, currentRoute } = useNavigation();
19
- const plugins = usePlugins();
19
+ const { plugins } = usePickFromState();
20
20
 
21
21
  return useEffect(() => {
22
22
  // TODD: typing problem: fix any type
@@ -1,5 +1,5 @@
1
1
  import EventEmitter from "events";
2
- import { renderHook, act } from "@testing-library/react-native";
2
+ import { renderHook, act } from "@testing-library/react-hooks";
3
3
  import { useAppState } from "../useAppState";
4
4
 
5
5
  const emitter = new EventEmitter();
@@ -1,4 +1,4 @@
1
- import { renderHook } from "@testing-library/react-native";
1
+ import { renderHook } from "@testing-library/react-hooks";
2
2
 
3
3
  jest.mock(
4
4
  "@applicaster/zapp-react-native-ui-components/Contexts/ScreenTrackedViewPositionsContext",
@@ -1,6 +1,7 @@
1
1
  import React from "react";
2
2
 
3
- import { renderHook, act, waitFor } from "@testing-library/react-native";
3
+ import { renderHook } from "@testing-library/react-hooks";
4
+ import { act, waitFor } from "@testing-library/react-native";
4
5
  import { Provider } from "react-redux";
5
6
  import configureStore from "redux-mock-store";
6
7
  import { useTrackedView } from "../useTrackedView";
@@ -0,0 +1 @@
1
+ export { useIsCasting } from "./useIsCasting";
@@ -0,0 +1,57 @@
1
+ import React from "react";
2
+ import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
3
+ import { useAppState } from "@applicaster/zapp-react-native-utils/reactHooks/app";
4
+ import { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
5
+
6
+ const CHROMECAST_IDENTIFIER = "quick-brick-chromecast-action";
7
+
8
+ const fakeEntry: any = {
9
+ title: "Chromecast use is casting fake entry",
10
+ id: "quick-brick-use-is-casting-fake-entry",
11
+ };
12
+
13
+ /**
14
+ * Returns the cast action state connection status, does not update when app is in background on Apple platforms
15
+ * On other platforms, it continues to check the cast action state even when the app is in background
16
+ */
17
+ export const useIsCasting = () => {
18
+ const actionContext = useActions(CHROMECAST_IDENTIFIER);
19
+ const appState = useAppState();
20
+ const isAppInForeground = appState === "active";
21
+ const shouldOnlyUpdateInForeground = isApplePlatform();
22
+
23
+ const [castActionState, setCastActionState] = React.useState(
24
+ !actionContext ? null : actionContext.initialEntryState(fakeEntry)
25
+ );
26
+
27
+ React.useEffect(() => {
28
+ if (!shouldOnlyUpdateInForeground || isAppInForeground) {
29
+ if (actionContext && castActionState === null) {
30
+ setCastActionState(actionContext.initialEntryState(fakeEntry));
31
+ }
32
+ }
33
+ }, [
34
+ actionContext,
35
+ setCastActionState,
36
+ isAppInForeground,
37
+ shouldOnlyUpdateInForeground,
38
+ ]);
39
+
40
+ React.useEffect(() => {
41
+ if (typeof actionContext?.addListener === "function") {
42
+ return actionContext?.addListener(String(fakeEntry.id), (state) => {
43
+ // Only check foreground state on Apple platforms
44
+ if (!shouldOnlyUpdateInForeground || isAppInForeground) {
45
+ setCastActionState(state);
46
+ }
47
+ });
48
+ }
49
+ }, [
50
+ setCastActionState,
51
+ actionContext,
52
+ isAppInForeground,
53
+ shouldOnlyUpdateInForeground,
54
+ ]);
55
+
56
+ return !!castActionState?.connected;
57
+ };
@@ -1,8 +1,10 @@
1
1
  import * as React from "react";
2
- import { cleanup, renderHook, waitFor } from "@testing-library/react-native";
2
+ import { cleanup, renderHook } from "@testing-library/react-hooks";
3
3
  import { CellTapContext } from "@applicaster/zapp-react-native-ui-components/Contexts/CellTapContext";
4
4
  import { ActionExecutorContext } from "@applicaster/zapp-react-native-utils/actionsExecutor/ActionExecutorContext";
5
5
 
6
+ import { waitFor } from "@testing-library/react-native";
7
+
6
8
  const mockNavigator = {
7
9
  getPathname: () => "pathnameMock",
8
10
  push: jest.fn(),
@@ -0,0 +1,55 @@
1
+ import * as React from "react";
2
+
3
+ const layoutReducer = (state, { payload }) => {
4
+ return state.map((item, index, _state) => ({
5
+ height: index === payload.index ? payload.height : item.height,
6
+ y:
7
+ index > 0
8
+ ? _state.slice(0, index).reduce((acc, value) => acc + value.height, 0)
9
+ : 0,
10
+ }));
11
+ };
12
+
13
+ export const useComponentsLayout = (count, onFinishLoadingVisible) => {
14
+ const [itemsLayout, dispatchItemLayout] = React.useReducer(
15
+ layoutReducer,
16
+ Array.from({ length: count }, () => ({ height: 0, y: 0 }))
17
+ );
18
+
19
+ const notified = React.useRef(false);
20
+ const [listHeight, setListHeight] = React.useState<number>(0);
21
+
22
+ const onItemLayout = React.useCallback(
23
+ (index) => (event) => {
24
+ const { height } = event.nativeEvent.layout;
25
+ dispatchItemLayout({ payload: { index, height } });
26
+ },
27
+ []
28
+ );
29
+
30
+ const onListLayout = React.useCallback((event) => {
31
+ const { height } = event.nativeEvent.layout;
32
+ setListHeight(height);
33
+ }, []);
34
+
35
+ React.useEffect(() => {
36
+ if (!notified.current) {
37
+ const finishLoadingVisible = !!itemsLayout.find(
38
+ (item) => item.y > listHeight
39
+ );
40
+
41
+ if (finishLoadingVisible) {
42
+ notified.current = true;
43
+ onFinishLoadingVisible?.();
44
+ }
45
+ }
46
+ }, [itemsLayout, listHeight]);
47
+
48
+ return React.useMemo(
49
+ () => ({
50
+ onItemLayout,
51
+ onListLayout,
52
+ }),
53
+ []
54
+ );
55
+ };
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable react/prop-types */
2
2
  import React from "react";
3
- import { renderHook, cleanup } from "@testing-library/react-native";
3
+ import { renderHook, cleanup } from "@testing-library/react-hooks";
4
4
  import { ConfigurationContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ConfigutaionContext";
5
5
  import { useConfiguration } from "@applicaster/zapp-react-native-utils/reactHooks/configuration";
6
6
 
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import { renderHook, cleanup } from "@testing-library/react-native";
2
+ import { renderHook, cleanup } from "@testing-library/react-hooks";
3
3
  import { NetworkStatusContext } from "@applicaster/zapp-react-native-ui-components/Contexts/NetworkStatusContext";
4
4
 
5
5
  const MockNetInfo = {
@@ -27,20 +27,20 @@ describe("Debug utils", () => {
27
27
  it("should create new timer if timer with a label does not exist", () => {
28
28
  performanceNowMock.mockReturnValue(1000);
29
29
  time("timer1");
30
- expect(timers.timer1).toBeDefined();
31
- expect(timers.timer1.startTime).toBe(1000);
30
+ expect(timers["timer1"]).toBeDefined();
31
+ expect(timers["timer1"].startTime).toBe(1000);
32
32
  });
33
33
 
34
34
  it("should update start time of timer if timer with a label exists", () => {
35
35
  // First call to time()
36
36
  performanceNowMock.mockReturnValue(1000);
37
37
  time("timer1");
38
- const previousStartTime = timers.timer1.startTime;
38
+ const previousStartTime = timers["timer1"].startTime;
39
39
 
40
40
  // Second call to time()
41
41
  performanceNowMock.mockReturnValue(2000);
42
42
  time("timer1");
43
- const currentStartTime = timers.timer1.startTime;
43
+ const currentStartTime = timers["timer1"].startTime;
44
44
 
45
45
  expect(previousStartTime).toBe(1000);
46
46
  expect(currentStartTime).toBe(2000);
@@ -1,23 +1,28 @@
1
1
  /* eslint react-native/split-platform-components: off */
2
2
 
3
3
  import { Platform, PlatformIOSStatic, NativeModules } from "react-native";
4
+ import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
5
+ import { F as alwaysFalse } from "ramda";
4
6
  import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
5
7
 
6
- export const useIsTablet = (): boolean => {
7
- const platform = Platform.OS;
8
+ const useIsTabletIos = (): boolean => {
9
+ const { isPad } = Platform as PlatformIOSStatic;
8
10
 
9
- if (platform === "ios") {
10
- const { isPad } = Platform as PlatformIOSStatic;
11
-
12
- return Boolean(isPad);
13
- }
14
-
15
- if (platform === "android") {
16
- const initialProps =
17
- NativeModules.QuickBrickCommunicationModule?.initialProps;
11
+ return isPad;
12
+ };
18
13
 
19
- return toBooleanWithDefaultFalse(initialProps?.is_tablet);
20
- }
14
+ const useIsTabletAndroid = (): boolean => {
15
+ const { initialProps } = NativeModules.QuickBrickCommunicationModule;
21
16
 
22
- return false;
17
+ return toBooleanWithDefaultFalse(initialProps?.is_tablet);
23
18
  };
19
+
20
+ export const useIsTablet = platformSelect({
21
+ ios: useIsTabletIos,
22
+ android: useIsTabletAndroid,
23
+ lg_tv: alwaysFalse,
24
+ samsung_tv: alwaysFalse,
25
+ android_tv: alwaysFalse,
26
+ tvos: alwaysFalse,
27
+ default: alwaysFalse,
28
+ });
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
+ import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
2
3
  import { useIsTablet } from "./useIsTablet";
3
- import { useAppData } from "@applicaster/zapp-react-native-redux";
4
4
 
5
5
  export const useMemoizedIsTablet = () => {
6
6
  const isTablet = useIsTablet();
@@ -14,9 +14,9 @@ export const useMemoizedIsTablet = () => {
14
14
  };
15
15
 
16
16
  export const useIsTabletLandscape = (): boolean => {
17
- const { isTabletPortrait } = useAppData();
17
+ const { appData } = usePickFromState(["appData"]);
18
18
 
19
19
  const isTablet = useIsTablet();
20
20
 
21
- return isTablet && !isTabletPortrait;
21
+ return isTablet && !appData.isTabletPortrait;
22
22
  };
@@ -0,0 +1,20 @@
1
+ import * as React from "react";
2
+ import { scrollEndReached$ } from "@applicaster/zapp-react-native-ui-components/events";
3
+ import { filter } from "rxjs/operators";
4
+
5
+ /**
6
+ * Subscribe to scroll-end-reached and call loadNextData only when this item is last in the river.
7
+ * Use in Grid/List so they load next page when the user scrolls to the end (river or tabs scroll container).
8
+ */
9
+ export function useScrollEndReachedLoadNext(
10
+ isLast: boolean,
11
+ loadNextData: () => void
12
+ ): void {
13
+ React.useEffect(() => {
14
+ const sub = scrollEndReached$.pipe(filter(() => isLast)).subscribe(() => {
15
+ loadNextData();
16
+ });
17
+
18
+ return () => sub.unsubscribe();
19
+ }, [isLast, loadNextData]);
20
+ }
@@ -1,7 +1,8 @@
1
- import { renderHook, waitFor } from "@testing-library/react-native";
1
+ import { renderHook } from "@testing-library/react-hooks";
2
2
  import { allFeedsIsReady, useBatchLoading } from "../useBatchLoading";
3
3
  import { WrappedWithProviders } from "@applicaster/zapp-react-native-utils/testUtils";
4
4
  import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
5
+ import { waitFor } from "@testing-library/react-native";
5
6
 
6
7
  jest.mock("../../navigation");
7
8
 
@@ -15,11 +16,7 @@ jest.mock(
15
16
  })
16
17
  );
17
18
 
18
- const getWrapper =
19
- (store) =>
20
- ({ children }) => (
21
- <WrappedWithProviders store={store}>{children}</WrappedWithProviders>
22
- );
19
+ const wrapper = WrappedWithProviders;
23
20
 
24
21
  describe("useBatchLoading", () => {
25
22
  const data = [
@@ -66,7 +63,8 @@ describe("useBatchLoading", () => {
66
63
  const riverId = "123";
67
64
 
68
65
  renderHook(() => useBatchLoading(data, { initialBatchSize, riverId }), {
69
- wrapper: getWrapper(store),
66
+ wrapper,
67
+ initialProps: { store },
70
68
  });
71
69
 
72
70
  const actions = (appStore.getStore() as any).getActions();
@@ -86,7 +84,7 @@ describe("useBatchLoading", () => {
86
84
  });
87
85
  });
88
86
 
89
- it("loadPipesData start loading new feed when 1 feed is done loading and 1 is in loading state", async () => {
87
+ it("loadPipesData start loading new feed when 1 feed is done loading and 1 is in loading state", () => {
90
88
  const store = {
91
89
  zappPipes: {
92
90
  url1: {
@@ -112,14 +110,13 @@ describe("useBatchLoading", () => {
112
110
  const riverId = "123";
113
111
 
114
112
  renderHook(() => useBatchLoading(data, { initialBatchSize, riverId }), {
115
- wrapper: getWrapper(store),
113
+ wrapper,
114
+ initialProps: { store },
116
115
  });
117
116
 
118
117
  const actions = (appStore.getStore() as any).getActions();
119
118
 
120
- await waitFor(() => {
121
- expect(actions).toHaveLength(1);
122
- });
119
+ expect(actions).toHaveLength(1);
123
120
 
124
121
  expect(actions[0]).toMatchObject({
125
122
  type: "ZAPP_PIPES_REQUEST_START",
@@ -127,7 +124,7 @@ describe("useBatchLoading", () => {
127
124
  });
128
125
  });
129
126
 
130
- it("loadPipesData has been called when no data cached", async () => {
127
+ it("loadPipesData has been called when no data cached", () => {
131
128
  const store = {
132
129
  zappPipes: {},
133
130
  test: "true",
@@ -137,17 +134,16 @@ describe("useBatchLoading", () => {
137
134
  const riverId = "123";
138
135
 
139
136
  renderHook(() => useBatchLoading(data, { initialBatchSize, riverId }), {
140
- wrapper: getWrapper(store),
137
+ wrapper,
138
+ initialProps: { store },
141
139
  });
142
140
 
143
141
  const actions = (appStore.getStore() as any).getActions();
144
142
 
145
- await waitFor(() => {
146
- expect(actions).toHaveLength(3);
147
- });
143
+ expect(actions).toHaveLength(3);
148
144
  });
149
145
 
150
- it("initial batch ready when all initial items loaded", async () => {
146
+ it("initial batch ready when all initial items loaded", () => {
151
147
  const store = {
152
148
  zappPipes: {
153
149
  url1: {
@@ -173,15 +169,13 @@ describe("useBatchLoading", () => {
173
169
 
174
170
  const { result } = renderHook(
175
171
  () => useBatchLoading(data, { initialBatchSize, riverId }),
176
- { wrapper: getWrapper(store) }
172
+ { wrapper, initialProps: { store } }
177
173
  );
178
174
 
179
- await waitFor(() => {
180
- expect(result.current).toBe(true);
181
- });
175
+ expect(result.current).toBe(true);
182
176
  });
183
177
 
184
- it("gallery-qb: loadPipesData should be called only once for first component in the gallery", async () => {
178
+ it("gallery-qb: loadPipesData should be called only once for first component in the gallery", () => {
185
179
  const store = {
186
180
  zappPipes: {},
187
181
  test: "true",
@@ -204,17 +198,16 @@ describe("useBatchLoading", () => {
204
198
  ];
205
199
 
206
200
  renderHook(() => useBatchLoading(data, { initialBatchSize, riverId }), {
207
- wrapper: getWrapper(store),
201
+ wrapper,
202
+ initialProps: { store },
208
203
  });
209
204
 
210
205
  const actions = (appStore.getStore() as any).getActions();
211
206
 
212
- await waitFor(() => {
213
- expect(actions).toHaveLength(1);
214
- });
207
+ expect(actions).toHaveLength(1);
215
208
  });
216
209
 
217
- it("gallery-qb: initial batch ready when all initial items loaded", async () => {
210
+ it("gallery-qb: initial batch ready when all initial items loaded", () => {
218
211
  const store = {
219
212
  zappPipes: {
220
213
  url1: {
@@ -240,12 +233,10 @@ describe("useBatchLoading", () => {
240
233
 
241
234
  const { result } = renderHook(
242
235
  () => useBatchLoading(data, { initialBatchSize, riverId }),
243
- { wrapper: getWrapper(store) }
236
+ { wrapper, initialProps: { store } }
244
237
  );
245
238
 
246
- await waitFor(() => {
247
- expect(result.current).toBe(true);
248
- });
239
+ expect(result.current).toBe(true);
249
240
  });
250
241
  });
251
242
 
@@ -1,4 +1,4 @@
1
- import { renderHook, waitFor } from "@testing-library/react-native";
1
+ import { renderHook } from "@testing-library/react-hooks";
2
2
  import { encodeParams } from "@applicaster/zapp-pipes-v2-client/src/utils";
3
3
 
4
4
  const appData = {
@@ -42,26 +42,26 @@ describe("useBuildPipesUrl", () => {
42
42
  build: jest.fn(() => Promise.resolve(buildResult)),
43
43
  };
44
44
 
45
- const { result } = renderHook(() =>
45
+ const { result, waitForNextUpdate } = renderHook(() =>
46
46
  useBuildPipesUrl({ url, mapping, requestBuilder: mockedRequestBuilder })
47
47
  );
48
48
 
49
49
  expect(result.current.url).toBe("http://foo.com/show/show-1");
50
50
  expect(result.current.requestParams).toBeNull();
51
51
 
52
- await waitFor(() => {
53
- expect(result.current.requestParams).toEqual(
54
- expect.objectContaining({
55
- params: {
56
- version: appData.version_name,
57
- ctx: encodeParams({ platform: appData.platform }),
58
- },
59
- headers: {
60
- SDK: appData.sdk_version,
61
- },
62
- })
63
- );
64
- });
52
+ await waitForNextUpdate();
53
+
54
+ expect(result.current.requestParams).toEqual(
55
+ expect.objectContaining({
56
+ params: {
57
+ version: appData.version_name,
58
+ ctx: encodeParams({ platform: appData.platform }),
59
+ },
60
+ headers: {
61
+ SDK: appData.sdk_version,
62
+ },
63
+ })
64
+ );
65
65
  });
66
66
 
67
67
  it("returns empty object if url is not in endpoints", async () => {
@@ -81,15 +81,15 @@ describe("useBuildPipesUrl", () => {
81
81
  build: jest.fn(() => Promise.resolve(buildResult)),
82
82
  };
83
83
 
84
- const { result } = renderHook(() =>
84
+ const { result, waitForNextUpdate } = renderHook(() =>
85
85
  useBuildPipesUrl({ url, mapping, requestBuilder: mockedRequestBuilder })
86
86
  );
87
87
 
88
88
  expect(result.current.url).toBe("http://foobar.com/show-1");
89
89
  expect(result.current.requestParams).toBeNull();
90
90
 
91
- await waitFor(() => {
92
- expect(result.current.requestParams).toEqual({});
93
- });
91
+ await waitForNextUpdate();
92
+
93
+ expect(result.current.requestParams).toEqual({});
94
94
  });
95
95
  });