@applicaster/zapp-react-native-ui-components 15.1.4 → 16.0.0-alpha.1042942411

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 (202) hide show
  1. package/Components/BackgroundImage/index.ts +1 -8
  2. package/Components/BaseFocusable/index.ios.ts +12 -2
  3. package/Components/Cell/FocusableWrapper.tsx +3 -0
  4. package/Components/Cell/TvOSCellComponent.tsx +6 -3
  5. package/Components/CellRendererResolver/index.ts +1 -1
  6. package/Components/Focusable/Focusable.tsx +4 -2
  7. package/Components/Focusable/FocusableTvOS.tsx +18 -1
  8. package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +1 -0
  9. package/Components/FocusableGroup/FocusableTvOS.tsx +30 -1
  10. package/Components/GeneralContentScreen/GeneralContentScreenHookAdapter.tsx +39 -0
  11. package/Components/GeneralContentScreen/__tests__/GeneralContentScreenHookAdapter.test.tsx +64 -0
  12. package/Components/GeneralContentScreen/__tests__/HookContentFocusGroup.web.test.tsx +91 -0
  13. package/Components/GeneralContentScreen/hookAdapter/__tests__/networkService.test.ts +74 -0
  14. package/Components/GeneralContentScreen/hookAdapter/__tests__/runInBackground.test.ts +139 -0
  15. package/Components/GeneralContentScreen/hookAdapter/__tests__/validationHelper.test.ts +124 -0
  16. package/Components/GeneralContentScreen/hookAdapter/logger.ts +6 -0
  17. package/Components/GeneralContentScreen/hookAdapter/networkService.ts +53 -0
  18. package/Components/GeneralContentScreen/hookAdapter/runInBackground.ts +48 -0
  19. package/Components/GeneralContentScreen/hookAdapter/validationHelper.ts +72 -0
  20. package/Components/GeneralContentScreen/hookFocus/index.tsx +13 -0
  21. package/Components/GeneralContentScreen/hookFocus/index.web.tsx +69 -0
  22. package/Components/GeneralContentScreen/index.ts +2 -0
  23. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
  24. package/Components/HandlePlayable/HandlePlayable.tsx +13 -8
  25. package/Components/Layout/TV/NavBarContainer.tsx +1 -10
  26. package/Components/Layout/TV/ScreenContainer.tsx +2 -6
  27. package/Components/Layout/TV/__tests__/__snapshots__/NavBarContainer.test.tsx.snap +7 -12
  28. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +7 -12
  29. package/Components/Layout/TV/index.tsx +3 -4
  30. package/Components/Layout/TV/index.web.tsx +3 -4
  31. package/Components/LinkHandler/LinkHandler.tsx +2 -2
  32. package/Components/MasterCell/CONFIG_BUILDER_TO_REACT_COMPONENT.md +144 -0
  33. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +27 -48
  34. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/model.test.ts +80 -0
  35. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +213 -0
  36. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/selectors.test.ts +45 -0
  37. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/style.test.ts +49 -0
  38. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +165 -0
  39. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +405 -0
  40. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/index.ts +1 -0
  41. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/model.ts +47 -0
  42. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +198 -0
  43. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/selectors.ts +26 -0
  44. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/style.ts +29 -0
  45. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/types.ts +37 -0
  46. package/Components/MasterCell/DefaultComponents/BorderContainerView/__tests__/index.test.tsx +217 -46
  47. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +50 -119
  48. package/Components/MasterCell/DefaultComponents/Button.tsx +50 -58
  49. package/Components/MasterCell/DefaultComponents/ButtonContainerView/components/HorizontalSeparator.tsx +8 -0
  50. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.android.tv.tsx +58 -0
  51. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tsx +15 -0
  52. package/Components/MasterCell/DefaultComponents/{tv/ButtonContainerView/index.tsx → ButtonContainerView/index.tv.tsx} +3 -11
  53. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.web.ts +1 -0
  54. package/Components/MasterCell/DefaultComponents/ButtonContainerView/types.ts +40 -0
  55. package/Components/MasterCell/DefaultComponents/CellBadge/__tests__/__snapshots__/index.test.tsx.snap +18 -0
  56. package/Components/MasterCell/DefaultComponents/CellBadge/__tests__/index.test.tsx +90 -0
  57. package/Components/MasterCell/DefaultComponents/CellBadge/index.tsx +13 -14
  58. package/Components/MasterCell/DefaultComponents/DataProvider/index.tsx +163 -0
  59. package/Components/MasterCell/DefaultComponents/FocusableView/index.android.tsx +2 -23
  60. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -22
  61. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +8 -2
  62. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +11 -3
  63. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +9 -1
  64. package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +15 -14
  65. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -3
  66. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +1 -2
  67. package/Components/MasterCell/DefaultComponents/PressableView.tsx +54 -0
  68. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +46 -50
  69. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +47 -39
  70. package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +7 -6
  71. package/Components/MasterCell/DefaultComponents/SecondaryImage/utils.ts +19 -6
  72. package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +15 -0
  73. package/Components/MasterCell/DefaultComponents/Text/index.tsx +45 -15
  74. package/Components/MasterCell/DefaultComponents/UIKitAdapters/Badge.tsx +49 -0
  75. package/Components/MasterCell/DefaultComponents/__tests__/ActionButton.test.tsx +122 -0
  76. package/Components/MasterCell/DefaultComponents/__tests__/Button.test.tsx +186 -0
  77. package/Components/MasterCell/DefaultComponents/__tests__/DataProvider.test.tsx +141 -0
  78. package/Components/MasterCell/DefaultComponents/__tests__/DynamicBadge.test.tsx +156 -0
  79. package/Components/MasterCell/DefaultComponents/__tests__/__snapshots__/ActionButton.test.tsx.snap +60 -0
  80. package/Components/MasterCell/DefaultComponents/__tests__/text.test.tsx +196 -67
  81. package/Components/MasterCell/DefaultComponents/index.ts +13 -3
  82. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +189 -0
  83. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +33 -0
  84. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +33 -0
  85. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +159 -0
  86. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +18 -0
  87. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabel.ts +67 -0
  88. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +39 -0
  89. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +402 -0
  90. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +141 -0
  91. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +354 -0
  92. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/helpers.ts +105 -0
  93. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +129 -0
  94. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +123 -0
  95. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +238 -0
  96. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Asset.ts +4 -18
  97. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Button.ts +24 -73
  98. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TextLabelsContainer.ts +37 -18
  99. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TvActionButton.tsx +27 -0
  100. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +89 -0
  101. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/renderedTree.test.tsx +231 -0
  102. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +47 -48
  103. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +115 -29
  104. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/insertButtonsBetweenLabels.test.ts +45 -13
  105. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +101 -144
  106. package/Components/MasterCell/MappingFunctions/index.js +3 -2
  107. package/Components/MasterCell/README.md +4 -0
  108. package/Components/MasterCell/__tests__/__snapshots__/dataAdapter.test.js.snap +24 -0
  109. package/Components/MasterCell/__tests__/configInflater.test.js +1 -0
  110. package/Components/MasterCell/__tests__/elementMapper.test.js +46 -0
  111. package/Components/MasterCell/contexts/PressedStateContext.ts +3 -0
  112. package/Components/MasterCell/dataAdapter.ts +4 -1
  113. package/Components/MasterCell/elementMapper.tsx +54 -7
  114. package/Components/MasterCell/hooks/index.ts +2 -0
  115. package/Components/MasterCell/hooks/usePressedState.ts +4 -0
  116. package/Components/MasterCell/index.tsx +20 -4
  117. package/Components/MasterCell/utils/__tests__/cloneChildrenWithIds.test.tsx +43 -0
  118. package/Components/MasterCell/utils/__tests__/useFilterChildren.test.tsx +80 -0
  119. package/Components/MasterCell/utils/index.ts +82 -16
  120. package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +33 -69
  121. package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +25 -47
  122. package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +122 -39
  123. package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +671 -0
  124. package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +257 -88
  125. package/Components/ModalComponent/AudioPlayer/Components/index.ts +6 -4
  126. package/Components/ModalComponent/BottomSheetDragArea.tsx +33 -0
  127. package/Components/ModalComponent/BottomSheetModalContent.tsx +17 -10
  128. package/Components/ModalComponent/__tests__/BottomSheetDragArea.test.tsx +74 -0
  129. package/Components/OfflineHandler/__tests__/__snapshots__/index.test.tsx.snap +4 -85
  130. package/Components/OfflineHandler/__tests__/index.test.tsx +101 -32
  131. package/Components/OfflineHandler/hooks.ts +123 -0
  132. package/Components/OfflineHandler/index.tsx +51 -73
  133. package/Components/OfflineHandler/utils/index.ts +1 -13
  134. package/Components/PlayerContainer/PlayerContainer.tsx +6 -5
  135. package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +2 -4
  136. package/Components/PreloaderWrapper/index.tsx +1 -1
  137. package/Components/River/ComponentsMap/ComponentsMap.tsx +8 -21
  138. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
  139. package/Components/River/RefreshControl.tsx +19 -105
  140. package/Components/River/River.tsx +9 -82
  141. package/Components/River/TV/River.tsx +31 -14
  142. package/Components/River/TV/index.tsx +8 -4
  143. package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +30 -0
  144. package/Components/River/TV/utils/index.ts +4 -0
  145. package/Components/River/TV/withFocusableGroupForContent.tsx +71 -0
  146. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +4 -0
  147. package/Components/River/__tests__/componentsMap.test.js +38 -0
  148. package/Components/River/hooks/__tests__/usePullToRefresh.test.ts +132 -0
  149. package/Components/River/hooks/index.ts +1 -0
  150. package/Components/River/hooks/usePullToRefresh.ts +51 -0
  151. package/Components/Screen/TV/index.web.tsx +4 -2
  152. package/Components/Screen/__tests__/Screen.test.tsx +65 -42
  153. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +68 -44
  154. package/Components/Screen/hooks.ts +2 -3
  155. package/Components/Screen/index.tsx +2 -3
  156. package/Components/Screen/orientationHandler.ts +3 -3
  157. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +5 -3
  158. package/Components/ScreenResolver/index.tsx +9 -5
  159. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +1 -1
  160. package/Components/ScreenRevealManager/ScreenRevealManager.ts +40 -8
  161. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +86 -69
  162. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +1 -1
  163. package/Components/Tabs/TabContent.tsx +7 -4
  164. package/Components/TopCutoffOverlay/__tests__/TopCutoffOverlay.test.tsx +201 -0
  165. package/Components/TopCutoffOverlay/hooks/__tests__/useMarginTop.test.ts +130 -0
  166. package/Components/TopCutoffOverlay/hooks/index.ts +1 -0
  167. package/Components/TopCutoffOverlay/hooks/useMarginTop.ts +59 -0
  168. package/Components/TopCutoffOverlay/index.tsx +55 -0
  169. package/Components/Transitioner/index.js +3 -3
  170. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +5 -5
  171. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +15 -7
  172. package/Components/VideoModal/utils.ts +12 -10
  173. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
  174. package/Components/Viewport/ViewportAware/index.tsx +16 -7
  175. package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
  176. package/Components/default-cell-renderer/viewTrees/mobile/index.ts +0 -3
  177. package/Contexts/CachedDimensionsContext/__tests__/index.test.ts +154 -0
  178. package/Contexts/ScreenContext/index.tsx +25 -18
  179. package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
  180. package/Decorators/Analytics/index.tsx +6 -5
  181. package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +1 -0
  182. package/Decorators/ConfigurationWrapper/const.ts +1 -0
  183. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
  184. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
  185. package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
  186. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
  187. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
  188. package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +80 -0
  189. package/Helpers/DataSourceHelper/index.ts +19 -0
  190. package/package.json +5 -5
  191. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +0 -135
  192. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/types.ts +0 -25
  193. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +0 -112
  194. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +0 -107
  195. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +0 -153
  196. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +0 -67
  197. package/Components/OfflineHandler/NotificationView/utils.ts +0 -34
  198. package/Components/River/TV/withTVEventHandler.tsx +0 -36
  199. package/Helpers/DataSourceHelper/index.js +0 -19
  200. /package/Components/BackgroundImage/{BackgroundImage.tv.android.tsx → BackgroundImage.android.tv.tsx} +0 -0
  201. /package/Components/BackgroundImage/{BackgroundImage.tv.ios.tsx → BackgroundImage.ios.tv.tsx} +0 -0
  202. /package/Components/BackgroundImage/{BackgroundImage.tv.web.tsx → BackgroundImage.tsx} +0 -0
@@ -9,7 +9,7 @@ import { useScreenConfiguration } from "../useScreenConfiguration";
9
9
  import { RefreshControl } from "../RefreshControl";
10
10
  import { ifEmptyUseFallback } from "@applicaster/zapp-react-native-utils/cellUtils";
11
11
  import {
12
- usePipesCacheReset,
12
+ useMarkPipesDataStale,
13
13
  useProfilerLogging,
14
14
  } from "@applicaster/zapp-react-native-utils/reactHooks";
15
15
  import { useLoadingState } from "./hooks/useLoadingState";
@@ -39,8 +39,6 @@ type Props = {
39
39
  scrollViewExtraProps?: {};
40
40
  riverId?: string;
41
41
  getStaticComponentFeed: any;
42
- pullToRefreshPipesV1RefreshingStateUpdater: () => boolean;
43
- refreshingPipesV1?: boolean;
44
42
  stickyHeaderIndices?: number[];
45
43
  };
46
44
 
@@ -65,10 +63,6 @@ function ComponentsMapComponent(props: Props) {
65
63
  groupId,
66
64
  riverId,
67
65
  getStaticComponentFeed,
68
- // Method added to keep pipes v1 logic up to date with the pullToRefresh state.
69
- // TODO: Remove when pipes v1 is deprecated.
70
- pullToRefreshPipesV1RefreshingStateUpdater,
71
- refreshingPipesV1,
72
66
  stickyHeaderIndices,
73
67
  } = props;
74
68
 
@@ -160,19 +154,11 @@ function ComponentsMapComponent(props: Props) {
160
154
  [flatListHeight]
161
155
  );
162
156
 
163
- usePipesCacheReset(riverId, riverComponents);
157
+ useMarkPipesDataStale(riverId, riverComponents);
164
158
 
165
159
  const refreshControl = React.useMemo(
166
- () =>
167
- pullToRefreshEnabled ? (
168
- <RefreshControl
169
- pullToRefreshPipesV1RefreshingStateUpdater={
170
- pullToRefreshPipesV1RefreshingStateUpdater
171
- }
172
- refreshingPipesV1={refreshingPipesV1}
173
- />
174
- ) : null,
175
- []
160
+ () => (pullToRefreshEnabled ? <RefreshControl /> : null),
161
+ [pullToRefreshEnabled]
176
162
  );
177
163
 
178
164
  const navBarStore = useScreenContextV2()._navBarStore;
@@ -281,6 +267,7 @@ function ComponentsMapComponent(props: Props) {
281
267
  >
282
268
  <ViewportTracker>
283
269
  <FlatList
270
+ testID="components-map-flat-list"
284
271
  ref={(ref) => {
285
272
  flatListRef.current = ref;
286
273
  }}
@@ -312,10 +299,10 @@ function ComponentsMapComponent(props: Props) {
312
299
  onScrollEndDrag={_onScrollEndDrag}
313
300
  scrollEventThrottle={16}
314
301
  {...scrollViewExtraProps}
315
- /* When wrapped in a parent ScrollView (e.g. tabs),
316
- this FlatList doesn't scroll so onEndReached can fire repeatedly;
317
- skip it here and let the parent ScrollView emit scroll-end instead. */
318
302
  onEndReached={
303
+ /* When wrapped in a parent ScrollView (e.g. tabs),
304
+ this FlatList doesn't scroll so onEndReached can fire repeatedly;
305
+ skip it here and let the parent ScrollView emit scroll-end instead. */
319
306
  isScreenWrappedInContainer
320
307
  ? undefined
321
308
  : () => {
@@ -1,4 +1,4 @@
1
- import { renderHook, act } from "@testing-library/react-hooks";
1
+ import { renderHook, act } from "@testing-library/react-native";
2
2
  import { BehaviorSubject } from "rxjs";
3
3
  import { useLoadingState } from "../useLoadingState";
4
4
 
@@ -4,9 +4,7 @@ import {
4
4
  RefreshControl as RNRefreshControl,
5
5
  StyleSheet,
6
6
  } from "react-native";
7
- import * as R from "ramda";
8
- import { path } from "@applicaster/zapp-react-native-utils/utils";
9
- import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
7
+ import { get } from "@applicaster/zapp-react-native-utils/utils";
10
8
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
11
9
  import { useLocalizedStrings } from "@applicaster/zapp-react-native-utils/localizationUtils";
12
10
  import { useAnalytics } from "@applicaster/zapp-react-native-utils/analyticsUtils";
@@ -15,8 +13,7 @@ import { useCurrentScreenData } from "@applicaster/zapp-react-native-utils/react
15
13
  import { useShallow } from "zustand/react/shallow";
16
14
  import { useScreenContextV2 } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
17
15
  import { useSafeAreaInsets } from "react-native-safe-area-context";
18
- import { useLoadPipesDataDispatch } from "@applicaster/zapp-react-native-utils/reactHooks";
19
- import { useGetUrlInflater } from "@applicaster/zapp-react-native-utils/reactHooks/feed/useInflatedUrl";
16
+ import { usePullToRefresh } from "./hooks";
20
17
 
21
18
  const BRIGHTNESS_THRESHOLD = 160;
22
19
  const ABOVE_DEFAULT_COLOR = "gray";
@@ -56,82 +53,6 @@ const getBrightness = (RGBcolor) => {
56
53
  );
57
54
  };
58
55
 
59
- export const usePullToRefresh = (
60
- riverComponents,
61
- pullToRefreshPipesV1RefreshingStateUpdater,
62
- refreshingPipesV1
63
- ) => {
64
- const isPipesV1 = !!pullToRefreshPipesV1RefreshingStateUpdater;
65
-
66
- const [refreshing, setRefreshing] = React.useState(false);
67
-
68
- const feedSources = React.useMemo(
69
- () =>
70
- (riverComponents || [])
71
- .map((riverComponent) => ({
72
- source: path(["data", "source"], riverComponent),
73
- mapping: path(["data", "mapping"], riverComponent),
74
- }))
75
- .filter(({ source }) => !isNilOrEmpty(source)),
76
- [riverComponents]
77
- );
78
-
79
- const feedsLength = feedSources.length;
80
-
81
- const [requestsCompletedCounter, setRequestsCompletedCounter] =
82
- React.useState(0);
83
-
84
- const loadPipesDataDispatcher = useLoadPipesDataDispatch();
85
- const urlInflater = useGetUrlInflater();
86
-
87
- React.useEffect(() => {
88
- // will not work for pipes v1 on 1st level screens
89
- if (refreshing && !isPipesV1) {
90
- feedSources.forEach(({ source, mapping }) => {
91
- const inflatedUrl = urlInflater(source, mapping);
92
-
93
- if (inflatedUrl) {
94
- loadPipesDataDispatcher(inflatedUrl, {
95
- silentRefresh: true,
96
- clearCache: true,
97
- callback: () => {
98
- setRequestsCompletedCounter(R.inc);
99
- },
100
- });
101
- } else {
102
- setRequestsCompletedCounter(R.inc);
103
- }
104
- });
105
- }
106
- }, [
107
- refreshing,
108
- isPipesV1,
109
- feedSources,
110
- loadPipesDataDispatcher,
111
- urlInflater,
112
- ]);
113
-
114
- React.useEffect(() => {
115
- if (requestsCompletedCounter === feedsLength) {
116
- setRefreshing(false);
117
- }
118
- }, [requestsCompletedCounter, feedsLength]);
119
-
120
- const onRefresh = React.useCallback(() => {
121
- if (isPipesV1) {
122
- pullToRefreshPipesV1RefreshingStateUpdater(true);
123
- } else {
124
- setRefreshing(true);
125
- setRequestsCompletedCounter(0);
126
- }
127
- }, [isPipesV1]);
128
-
129
- return {
130
- refreshing: isPipesV1 ? refreshingPipesV1 : refreshing,
131
- onRefresh,
132
- };
133
- };
134
-
135
56
  /** Returns the offset for the progress view of the RefreshControl component
136
57
  * based on navbar content position */
137
58
  export const useGetProgressViewOffset = () => {
@@ -160,16 +81,12 @@ export const useGetProgressViewOffset = () => {
160
81
  }
161
82
  };
162
83
 
163
- export function RefreshControl(props: {
164
- pullToRefreshPipesV1RefreshingStateUpdater?: (refreshing: boolean) => void;
165
- refreshingPipesV1?: boolean;
166
- }) {
84
+ export function RefreshControl(props) {
167
85
  const screenData = useCurrentScreenData();
168
86
 
169
87
  const { refreshing, onRefresh } = usePullToRefresh(
170
- screenData.ui_components,
171
- props.pullToRefreshPipesV1RefreshingStateUpdater,
172
- props.refreshingPipesV1
88
+ screenData.id,
89
+ screenData.ui_components
173
90
  );
174
91
 
175
92
  const { app_background_color: themeBackgroundColor } = useTheme();
@@ -187,29 +104,26 @@ export function RefreshControl(props: {
187
104
  displayTitleIOS,
188
105
  } = React.useMemo(
189
106
  () => ({
190
- indicatorColor: R.prop(
191
- "pull_to_refresh_indicator_color",
192
- screenData.styles
193
- ),
194
- titleUnderIndicatorColor: R.prop(
195
- "pull_to_refresh_title_color_under_indicator",
196
- screenData.styles
107
+ indicatorColor: get(screenData.styles, "pull_to_refresh_indicator_color"),
108
+ titleUnderIndicatorColor: get(
109
+ screenData.styles,
110
+ "pull_to_refresh_title_color_under_indicator"
197
111
  ),
198
- indicatorBackgroundColor: R.prop(
199
- "pull_to_refresh_indicator_bg_color",
200
- screenData.styles
112
+ indicatorBackgroundColor: get(
113
+ screenData.styles,
114
+ "pull_to_refresh_indicator_bg_color"
201
115
  ),
202
116
  indicatorSize:
203
- R.prop("pull_to_refresh_indicator_size", screenData.styles) === "large"
117
+ get(screenData.styles, "pull_to_refresh_indicator_size") === "large"
204
118
  ? "large"
205
119
  : "default",
206
- generalContentBackgroungColor: R.prop(
207
- "screen_background_color",
208
- screenData.styles
120
+ generalContentBackgroungColor: get(
121
+ screenData.styles,
122
+ "screen_background_color"
209
123
  ),
210
- displayTitleIOS: R.prop(
211
- "pull_to_refresh_display_title_ios",
212
- screenData.styles
124
+ displayTitleIOS: get(
125
+ screenData.styles,
126
+ "pull_to_refresh_display_title_ios"
213
127
  ),
214
128
  }),
215
129
  [screenData]
@@ -2,7 +2,6 @@ import * as React from "react";
2
2
  import * as R from "ramda";
3
3
  import { GeneralContentScreen } from "@applicaster/zapp-react-native-ui-components/Components/GeneralContentScreen";
4
4
 
5
- import { FeedLoader } from "../FeedLoader";
6
5
  import { ScreenResolver } from "../ScreenResolver";
7
6
 
8
7
  type Props = ZappScreenProps & {
@@ -23,24 +22,13 @@ type Props = ZappScreenProps & {
23
22
  river: ZappRiver;
24
23
  };
25
24
 
26
- type State = {
27
- refreshing: boolean;
28
- };
29
-
30
- export class RiverComponent extends React.Component<Props, State> {
25
+ export class RiverComponent extends React.Component<Props> {
31
26
  private currentScreenTitle: string;
32
27
  private currentScreenSummary: string;
33
28
  constructor(props: Props) {
34
29
  super(props);
35
30
 
36
- this.state = {
37
- refreshing: false,
38
- };
39
-
40
31
  this.applyContexts();
41
-
42
- this.pullToRefreshPipesV1RefreshingStateUpdater =
43
- this.pullToRefreshPipesV1RefreshingStateUpdater.bind(this);
44
32
  }
45
33
 
46
34
  applyContexts() {
@@ -69,20 +57,9 @@ export class RiverComponent extends React.Component<Props, State> {
69
57
  }
70
58
  }
71
59
 
72
- usesPipesV1Layout() {
73
- return this.props?.appData?.layoutVersion === "v1";
74
- }
75
-
76
- // Method added to keep pipes v1 logic up to date with the pullToRefresh state.
77
- // TODO: Remove when pipes v1 is deprecated.
78
- pullToRefreshPipesV1RefreshingStateUpdater(refreshing) {
79
- this.setState({ refreshing });
80
- }
81
-
82
60
  render() {
83
61
  const {
84
62
  river,
85
- feedUrl,
86
63
  screenData,
87
64
  isInsideContainer,
88
65
  groupId,
@@ -91,22 +68,10 @@ export class RiverComponent extends React.Component<Props, State> {
91
68
 
92
69
  const { id, type } = river;
93
70
 
94
- const connectedFeedURL = R.path(["content", "src"], screenData);
95
- const _feedUrl = feedUrl || connectedFeedURL;
96
-
97
71
  if (type !== "general_content") {
98
- let riverWithConnectedDatasource;
99
-
100
- if (_feedUrl && this.usesPipesV1Layout()) {
101
- riverWithConnectedDatasource = {
102
- ...river,
103
- data: R.merge(river.data || {}, { source: _feedUrl }),
104
- };
105
- }
106
-
107
72
  return (
108
73
  <ScreenResolver
109
- screenData={R.mergeLeft(riverWithConnectedDatasource || river, {
74
+ screenData={R.mergeLeft(river, {
110
75
  groupId,
111
76
  ...screenData,
112
77
  })}
@@ -116,53 +81,15 @@ export class RiverComponent extends React.Component<Props, State> {
116
81
  );
117
82
  }
118
83
 
119
- if (!_feedUrl || !this.usesPipesV1Layout()) {
120
- this.currentScreenTitle = (screenData && screenData.title) || null;
121
-
122
- return (
123
- <GeneralContentScreen
124
- screenId={id}
125
- isScreenWrappedInContainer={isInsideContainer}
126
- groupId={groupId}
127
- scrollViewExtraProps={scrollViewExtraProps}
128
- />
129
- );
130
- }
84
+ this.currentScreenTitle = (screenData && screenData.title) || null;
131
85
 
132
86
  return (
133
- <FeedLoader
134
- feedUrl={_feedUrl}
135
- refreshing={this.state.refreshing}
136
- refreshCallback={() =>
137
- this.pullToRefreshPipesV1RefreshingStateUpdater(false)
138
- }
139
- >
140
- {(feed) => {
141
- if (!feed) {
142
- return null;
143
- }
144
-
145
- this.currentScreenSummary = (feed && feed.summary) || null;
146
-
147
- this.currentScreenTitle =
148
- (feed && feed.title) || (screenData && screenData.title) || null;
149
-
150
- return (
151
- <GeneralContentScreen
152
- screenId={id}
153
- groupId={groupId}
154
- feed={feed}
155
- isScreenWrappedInContainer={isInsideContainer}
156
- scrollViewExtraProps={scrollViewExtraProps}
157
- componentsMapExtraProps={{
158
- pullToRefreshPipesV1RefreshingStateUpdater:
159
- this.pullToRefreshPipesV1RefreshingStateUpdater,
160
- refreshingPipesV1: this.state.refreshing,
161
- }}
162
- />
163
- );
164
- }}
165
- </FeedLoader>
87
+ <GeneralContentScreen
88
+ screenId={id}
89
+ isScreenWrappedInContainer={isInsideContainer}
90
+ groupId={groupId}
91
+ scrollViewExtraProps={scrollViewExtraProps}
92
+ />
166
93
  );
167
94
  }
168
95
  }
@@ -2,7 +2,8 @@
2
2
 
3
3
  import * as React from "react";
4
4
  import { Text } from "react-native";
5
- import * as R from "ramda";
5
+
6
+ import { mergeRight } from "@applicaster/zapp-react-native-utils/utils";
6
7
 
7
8
  import { GeneralContentScreen } from "../../GeneralContentScreen";
8
9
  import { ScreenResolver } from "@applicaster/zapp-react-native-ui-components/Components/ScreenResolver";
@@ -13,6 +14,8 @@ import {
13
14
  } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
14
15
  import { useRivers } from "@applicaster/zapp-react-native-utils/reactHooks/state";
15
16
 
17
+ import { toStringOrEmpty } from "./utils";
18
+
16
19
  type Props = {
17
20
  screenId: string;
18
21
  screenData: ZappRiver | ZappEntry;
@@ -24,6 +27,7 @@ type Props = {
24
27
  isInsideContainer?: boolean;
25
28
  extraAnchorPointYOffset: number;
26
29
  river?: ZappRiver | ZappEntry;
30
+ groupId: string;
27
31
  };
28
32
 
29
33
  export const River = (props: Props) => {
@@ -35,6 +39,7 @@ export const River = (props: Props) => {
35
39
  componentsMapExtraProps,
36
40
  isInsideContainer,
37
41
  extraAnchorPointYOffset,
42
+ groupId,
38
43
  } = props;
39
44
 
40
45
  const { title: screenTitle, summary: screenSummary } = useNavbarState();
@@ -51,28 +56,41 @@ export const River = (props: Props) => {
51
56
  [screenId]
52
57
  );
53
58
 
54
- const stringOrEmpty = (value: string | number | undefined): string =>
55
- R.isNil(value) ? "" : String(value);
59
+ const screenResolverData = React.useMemo(() => {
60
+ const extraData = mergeRight(extraProps, screenResolverExtraProps);
61
+
62
+ return {
63
+ extraData,
64
+ screenData: mergeRight(river, { groupId: extraData?.groupId }),
65
+ componentsMapExtraProps: mergeRight(componentsMapExtraProps, { groupId }),
66
+ };
67
+ }, [
68
+ extraProps,
69
+ screenResolverExtraProps,
70
+ river,
71
+ componentsMapExtraProps,
72
+ groupId,
73
+ ]);
56
74
 
57
75
  React.useEffect(() => {
58
76
  if (!isInsideContainer) {
59
- setScreenTitle(stringOrEmpty(screenData?.title));
60
- setScreenSummary(stringOrEmpty(screenData?.summary));
77
+ setScreenTitle(toStringOrEmpty(screenData?.title));
78
+ setScreenSummary(toStringOrEmpty(screenData?.summary));
61
79
  }
62
80
  }, [screenData.id]);
63
81
 
64
82
  React.useEffect(() => {
65
83
  if (feedData && !isInsideContainer) {
66
84
  if (feedData.title && feedData.title !== screenTitle) {
67
- setScreenTitle(stringOrEmpty(feedData.title));
85
+ setScreenTitle(toStringOrEmpty(feedData.title));
68
86
  }
69
87
 
70
88
  if (feedData.summary && feedData.summary !== screenSummary) {
71
- setScreenSummary(stringOrEmpty(feedData.summary));
89
+ setScreenSummary(toStringOrEmpty(feedData.summary));
72
90
  }
73
91
  } else {
74
- setScreenTitle(stringOrEmpty(screenData?.title));
75
- setScreenSummary(stringOrEmpty(screenData?.summary));
92
+ setScreenTitle(toStringOrEmpty(screenData?.title));
93
+ setScreenSummary(toStringOrEmpty(screenData?.summary));
76
94
  }
77
95
  }, [feedData, screenData, screenTitle, screenSummary]);
78
96
 
@@ -86,15 +104,13 @@ export const River = (props: Props) => {
86
104
  }
87
105
 
88
106
  if (river.type !== "general_content") {
89
- const extraData = { ...R.mergeRight(extraProps, screenResolverExtraProps) };
90
-
91
107
  return (
92
108
  <ScreenResolver
93
109
  screenType={river.type}
94
110
  screenId={screenId}
95
- screenData={R.merge(river, { groupId: extraData?.groupId })}
96
- componentsMapExtraProps={componentsMapExtraProps}
97
- {...extraData}
111
+ screenData={screenResolverData.screenData}
112
+ componentsMapExtraProps={screenResolverData.componentsMapExtraProps}
113
+ {...screenResolverData.extraData}
98
114
  />
99
115
  );
100
116
  }
@@ -106,6 +122,7 @@ export const River = (props: Props) => {
106
122
  isScreenWrappedInContainer={isInsideContainer}
107
123
  extraAnchorPointYOffset={extraAnchorPointYOffset}
108
124
  componentsMapExtraProps={componentsMapExtraProps}
125
+ groupId={groupId}
109
126
  />
110
127
  );
111
128
  };
@@ -1,11 +1,15 @@
1
- import { compose } from "ramda";
1
+ import { compose, identity } from "@applicaster/zapp-react-native-utils/utils";
2
+ import { isTvOSPlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
3
+
2
4
  import { River as RiverComponent } from "./River";
3
- import { withTvEventHandler } from "./withTVEventHandler";
4
5
  import { withComponentsMapOffsetContext } from "../../../Contexts/ComponentsMapOffsetContext";
5
6
  import { withRiverDataLoader } from "./withRiverDataLoader";
7
+ import { withFocusableGroupForContent } from "./withFocusableGroupForContent";
8
+
9
+ const isTVOS = isTvOSPlatform();
6
10
 
7
11
  export const River = compose(
8
- withTvEventHandler,
9
12
  withComponentsMapOffsetContext,
10
- withRiverDataLoader
13
+ withRiverDataLoader,
14
+ isTVOS ? withFocusableGroupForContent : identity
11
15
  )(RiverComponent);
@@ -0,0 +1,30 @@
1
+ import { toStringOrEmpty } from "..";
2
+
3
+ describe("toStringOrEmpty", () => {
4
+ test("returns empty string for undefined", () => {
5
+ expect(toStringOrEmpty(undefined)).toBe("");
6
+ });
7
+
8
+ test("returns empty string for null", () => {
9
+ expect(toStringOrEmpty(null)).toBe("");
10
+ });
11
+
12
+ test("converts number to string", () => {
13
+ expect(toStringOrEmpty(0)).toBe("0");
14
+ expect(toStringOrEmpty(123)).toBe("123");
15
+ expect(toStringOrEmpty(-42)).toBe("-42");
16
+ });
17
+
18
+ test("returns string as is", () => {
19
+ expect(toStringOrEmpty("hello")).toBe("hello");
20
+ expect(toStringOrEmpty("")).toBe("");
21
+ });
22
+
23
+ test("works with numeric strings", () => {
24
+ expect(toStringOrEmpty("123")).toBe("123");
25
+ });
26
+
27
+ test("does not throw on falsy values like 0", () => {
28
+ expect(toStringOrEmpty(0)).toBe("0");
29
+ });
30
+ });
@@ -0,0 +1,4 @@
1
+ import { isNil } from "@applicaster/zapp-react-native-utils/utils";
2
+
3
+ export const toStringOrEmpty = (value: unknown): string =>
4
+ isNil(value) ? "" : String(value);
@@ -0,0 +1,71 @@
1
+ import * as React from "react";
2
+ import { View, StyleSheet } from "react-native";
3
+
4
+ import { FocusableGroup } from "@applicaster/zapp-react-native-ui-components/Components/FocusableGroup";
5
+ import { riverFocusManager } from "@applicaster/zapp-react-native-utils/appUtils/RiverFocusManager";
6
+
7
+ import { topMenuLayoutChange$ } from "@applicaster/zapp-react-native-tvos-app/Layout/topMenu";
8
+
9
+ const styles = StyleSheet.create({
10
+ flexOne: {
11
+ flex: 1,
12
+ },
13
+ });
14
+
15
+ export const withFocusableGroupForContent = (Component) => {
16
+ return function WithFocusableGroupForContent(props) {
17
+ const { screenId, isInsideContainer } = props;
18
+
19
+ const [topMenuHeight, setTopMenuHeight] = React.useState(0);
20
+
21
+ React.useEffect(() => {
22
+ const subscription = topMenuLayoutChange$.subscribe((layout) => {
23
+ setTopMenuHeight(layout.height);
24
+ });
25
+
26
+ return () => {
27
+ subscription.unsubscribe();
28
+ };
29
+ }, []);
30
+
31
+ const focusableId = React.useMemo(
32
+ () =>
33
+ riverFocusManager.screenFocusableGroupId({
34
+ screenId,
35
+ isInsideContainer,
36
+ }),
37
+ [screenId, isInsideContainer]
38
+ );
39
+
40
+ if (isInsideContainer) {
41
+ return <Component {...props} />;
42
+ }
43
+
44
+ return (
45
+ <FocusableGroup
46
+ key={focusableId}
47
+ id={focusableId}
48
+ // The top menu is rendered in its own FocusableGroup, anchored at the top of the screen.
49
+ // When the "content" FocusableGroup starts at y = 0 as well, the two groups visually overlap.
50
+ // On TvOS platform this overlap can confuse the focus engine, because the focusable bounds of
51
+ // the top-menu group and the content group intersect, leading to erratic navigation between
52
+ // the menu and the content (e.g. unexpected jumps or focus getting "stuck").
53
+ //
54
+ // To avoid this, we shift the entire content FocusableGroup down by the dynamic top menu
55
+ // height (marginTop: topMenuHeight). This separates the focus regions of the two groups in
56
+ // focus space, so they no longer intersect.
57
+ //
58
+ // The inner <View> below then applies the inverse margin (marginTop: -topMenuHeight) so that
59
+ // the actual visual position of the content on screen does not change; only the focusable
60
+ // bounds of the outer group are offset.
61
+ style={[styles.flexOne, { marginTop: topMenuHeight }]}
62
+ // this group does not have parent
63
+ groupId={undefined}
64
+ >
65
+ <View style={[styles.flexOne, { marginTop: -1 * topMenuHeight }]}>
66
+ <Component {...props} groupId={focusableId} />
67
+ </View>
68
+ </FocusableGroup>
69
+ );
70
+ };
71
+ };
@@ -10,6 +10,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
10
10
  >
11
11
  <View
12
12
  onLayout={[Function]}
13
+ style={{}}
13
14
  >
14
15
  <RCTScrollView
15
16
  ListFooterComponent={
@@ -150,6 +151,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
150
151
  }
151
152
  }
152
153
  stickyHeaderIndices={[]}
154
+ testID="components-map-flat-list"
153
155
  viewabilityConfigCallbackPairs={[]}
154
156
  windowSize={12}
155
157
  >
@@ -161,6 +163,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
161
163
  >
162
164
  <View
163
165
  onLayout={[Function]}
166
+ style={{}}
164
167
  >
165
168
  <View />
166
169
  </View>
@@ -172,6 +175,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
172
175
  >
173
176
  <View
174
177
  onLayout={[Function]}
178
+ style={{}}
175
179
  />
176
180
  </View>
177
181
  <View