@applicaster/zapp-react-native-ui-components 15.1.0-rc.9 → 16.0.0-alpha.1740103407
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/Components/BackgroundImage/BackgroundImage.tsx +42 -0
- package/Components/BackgroundImage/BackgroundImage.tv.android.tsx +28 -0
- package/Components/BackgroundImage/BackgroundImage.tv.ios.tsx +24 -0
- package/Components/BackgroundImage/index.ts +1 -0
- package/Components/BaseFocusable/index.ios.ts +12 -2
- package/Components/Cell/FocusableWrapper.tsx +3 -0
- package/Components/Cell/TvOSCellComponent.tsx +6 -3
- package/Components/Focusable/Focusable.tsx +4 -2
- package/Components/Focusable/FocusableTvOS.tsx +18 -1
- package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +1 -0
- package/Components/FocusableGroup/FocusableTvOS.tsx +41 -8
- package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
- package/Components/HandlePlayable/HandlePlayable.tsx +13 -8
- package/Components/Layout/FullWidthRow.tsx +38 -0
- package/Components/Layout/TV/NavBarContainer.tsx +1 -10
- package/Components/Layout/TV/ScreenContainer.tsx +2 -6
- package/Components/Layout/TV/__tests__/__snapshots__/NavBarContainer.test.tsx.snap +7 -12
- package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +7 -12
- package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +0 -1
- package/Components/Layout/TV/index.tsx +5 -7
- package/Components/Layout/TV/index.web.tsx +5 -7
- package/Components/LinkHandler/LinkHandler.tsx +2 -2
- package/Components/MasterCell/CONFIG_BUILDER_TO_REACT_COMPONENT.md +144 -0
- package/Components/MasterCell/DefaultComponents/ActionButton.tsx +16 -5
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/model.test.ts +80 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +187 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/selectors.test.ts +45 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/style.test.ts +49 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +165 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +405 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/index.ts +1 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/model.ts +47 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +170 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/selectors.ts +26 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/style.ts +29 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/types.ts +37 -0
- package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +4 -10
- package/Components/MasterCell/DefaultComponents/Button.tsx +0 -15
- package/Components/MasterCell/DefaultComponents/ButtonContainerView/components/HorizontalSeparator.tsx +8 -0
- package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tsx +15 -0
- package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tv.android.tsx +58 -0
- package/Components/MasterCell/DefaultComponents/{tv/ButtonContainerView/index.tsx → ButtonContainerView/index.tv.tsx} +3 -11
- package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.web.ts +1 -0
- package/Components/MasterCell/DefaultComponents/ButtonContainerView/types.ts +40 -0
- package/Components/MasterCell/DefaultComponents/DataProvider/index.tsx +163 -0
- package/Components/MasterCell/DefaultComponents/FocusableView/index.android.tsx +2 -23
- package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -22
- package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +8 -2
- package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +11 -3
- package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +9 -1
- package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +15 -14
- package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +1 -2
- package/Components/MasterCell/DefaultComponents/PressableView.tsx +34 -0
- package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +7 -6
- package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +11 -0
- package/Components/MasterCell/DefaultComponents/__tests__/DataProvider.test.tsx +141 -0
- package/Components/MasterCell/DefaultComponents/index.ts +9 -3
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +135 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +33 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +22 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +125 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +16 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabel.ts +67 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +37 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +393 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +141 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +343 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/helpers.ts +105 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +122 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +118 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +238 -0
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Asset.ts +4 -18
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Button.ts +24 -73
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TextLabelsContainer.ts +37 -18
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TvActionButton.tsx +27 -0
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +89 -0
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/renderedTree.test.tsx +231 -0
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +47 -48
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +115 -29
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +101 -144
- package/Components/MasterCell/MappingFunctions/index.js +3 -2
- package/Components/MasterCell/README.md +4 -0
- package/Components/MasterCell/__tests__/__snapshots__/dataAdapter.test.js.snap +24 -0
- package/Components/MasterCell/__tests__/configInflater.test.js +1 -0
- package/Components/MasterCell/__tests__/elementMapper.test.js +46 -0
- package/Components/MasterCell/dataAdapter.ts +4 -1
- package/Components/MasterCell/elementMapper.tsx +52 -7
- package/Components/MasterCell/utils/__tests__/cloneChildrenWithIds.test.tsx +43 -0
- package/Components/MasterCell/utils/__tests__/useFilterChildren.test.tsx +80 -0
- package/Components/MasterCell/utils/index.ts +85 -15
- package/Components/OfflineHandler/NotificationView/NotificationView.tsx +2 -2
- package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +17 -18
- package/Components/OfflineHandler/__tests__/index.test.tsx +27 -18
- package/Components/PlayerContainer/PlayerContainer.tsx +8 -5
- package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +284 -0
- package/Components/River/ComponentsMap/ComponentsMap.tsx +188 -118
- package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
- package/Components/River/RefreshControl.tsx +19 -88
- package/Components/River/River.tsx +9 -82
- package/Components/River/TV/River.tsx +31 -14
- package/Components/River/TV/index.tsx +8 -4
- package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +30 -0
- package/Components/River/TV/utils/index.ts +4 -0
- package/Components/River/TV/withFocusableGroupForContent.tsx +71 -0
- package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +2 -22
- package/Components/River/__tests__/componentsMap.test.js +38 -0
- package/Components/River/hooks/__tests__/usePullToRefresh.test.ts +132 -0
- package/Components/River/hooks/index.ts +1 -0
- package/Components/River/hooks/usePullToRefresh.ts +51 -0
- package/Components/Screen/TV/index.web.tsx +4 -2
- package/Components/Screen/__tests__/Screen.test.tsx +65 -42
- package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +68 -44
- package/Components/Screen/hooks.ts +2 -3
- package/Components/Screen/index.tsx +2 -3
- package/Components/Screen/orientationHandler.ts +3 -3
- package/Components/ScreenResolver/index.tsx +9 -5
- package/Components/ScreenRevealManager/Overlay.tsx +34 -0
- package/Components/ScreenRevealManager/ScreenRevealManager.ts +40 -8
- package/Components/ScreenRevealManager/__tests__/Overlay.test.tsx +88 -0
- package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +86 -69
- package/Components/ScreenRevealManager/withScreenRevealManager.tsx +9 -20
- package/Components/Tabs/TabContent.tsx +7 -4
- package/Components/TopCutoffOverlay/__tests__/TopCutoffOverlay.test.tsx +201 -0
- package/Components/TopCutoffOverlay/hooks/__tests__/useMarginTop.test.ts +130 -0
- package/Components/TopCutoffOverlay/hooks/index.ts +1 -0
- package/Components/TopCutoffOverlay/hooks/useMarginTop.ts +59 -0
- package/Components/TopCutoffOverlay/index.tsx +55 -0
- package/Components/Transitioner/index.js +3 -3
- package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +5 -5
- package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +15 -7
- package/Components/VideoModal/utils.ts +12 -10
- package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
- package/Components/Viewport/ViewportAware/index.tsx +16 -7
- package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
- package/Components/ZappFrameworkComponents/BarView/BarView.tsx +4 -6
- package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +2 -2
- package/Components/default-cell-renderer/viewTrees/mobile/index.ts +0 -3
- package/Contexts/ScreenContext/index.tsx +25 -18
- package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
- package/Decorators/Analytics/index.tsx +6 -5
- package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +1 -0
- package/Decorators/ConfigurationWrapper/const.ts +1 -0
- package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
- package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
- package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
- package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
- package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
- package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +80 -0
- package/Helpers/DataSourceHelper/index.ts +19 -0
- package/Helpers/index.js +7 -1
- package/package.json +5 -5
- package/Components/Layout/TV/LayoutBackground.tsx +0 -28
- package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +0 -135
- package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/types.ts +0 -25
- package/Components/River/TV/withTVEventHandler.tsx +0 -36
- package/Helpers/DataSourceHelper/index.js +0 -19
|
@@ -4,9 +4,7 @@ import {
|
|
|
4
4
|
RefreshControl as RNRefreshControl,
|
|
5
5
|
StyleSheet,
|
|
6
6
|
} from "react-native";
|
|
7
|
-
import
|
|
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,7 +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 {
|
|
16
|
+
import { usePullToRefresh } from "./hooks";
|
|
19
17
|
|
|
20
18
|
const BRIGHTNESS_THRESHOLD = 160;
|
|
21
19
|
const ABOVE_DEFAULT_COLOR = "gray";
|
|
@@ -55,66 +53,6 @@ const getBrightness = (RGBcolor) => {
|
|
|
55
53
|
);
|
|
56
54
|
};
|
|
57
55
|
|
|
58
|
-
export const usePullToRefresh = (
|
|
59
|
-
riverComponents,
|
|
60
|
-
pullToRefreshPipesV1RefreshingStateUpdater,
|
|
61
|
-
refreshingPipesV1
|
|
62
|
-
) => {
|
|
63
|
-
const isPipesV1 = !!pullToRefreshPipesV1RefreshingStateUpdater;
|
|
64
|
-
|
|
65
|
-
const [refreshing, setRefreshing] = React.useState(false);
|
|
66
|
-
|
|
67
|
-
const feeds: string[] = React.useMemo(
|
|
68
|
-
() =>
|
|
69
|
-
(riverComponents || [])
|
|
70
|
-
.map((riverComponent) => path(["data", "source"], riverComponent))
|
|
71
|
-
.filter((feed) => !isNilOrEmpty(feed)),
|
|
72
|
-
[riverComponents]
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
const feedsLength = feeds.length;
|
|
76
|
-
|
|
77
|
-
const [requestsCompletedCounter, setRequestsCompletedCounter] =
|
|
78
|
-
React.useState(0);
|
|
79
|
-
|
|
80
|
-
const loadPipesDataDispatcher = useLoadPipesDataDispatch();
|
|
81
|
-
|
|
82
|
-
React.useEffect(() => {
|
|
83
|
-
// will not work for pipes v1 on 1st level screens
|
|
84
|
-
if (refreshing && !isPipesV1) {
|
|
85
|
-
feeds.forEach((feed) => {
|
|
86
|
-
loadPipesDataDispatcher(feed, {
|
|
87
|
-
silentRefresh: true,
|
|
88
|
-
clearCache: true,
|
|
89
|
-
callback: () => {
|
|
90
|
-
setRequestsCompletedCounter(R.inc);
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
}, [refreshing, isPipesV1, feeds, loadPipesDataDispatcher]);
|
|
96
|
-
|
|
97
|
-
React.useEffect(() => {
|
|
98
|
-
if (requestsCompletedCounter === feedsLength) {
|
|
99
|
-
setRefreshing(false);
|
|
100
|
-
}
|
|
101
|
-
}, [requestsCompletedCounter, feedsLength]);
|
|
102
|
-
|
|
103
|
-
const onRefresh = React.useCallback(() => {
|
|
104
|
-
if (isPipesV1) {
|
|
105
|
-
pullToRefreshPipesV1RefreshingStateUpdater(true);
|
|
106
|
-
} else {
|
|
107
|
-
setRefreshing(true);
|
|
108
|
-
setRequestsCompletedCounter(0);
|
|
109
|
-
}
|
|
110
|
-
}, [isPipesV1]);
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
refreshing: isPipesV1 ? refreshingPipesV1 : refreshing,
|
|
114
|
-
onRefresh,
|
|
115
|
-
};
|
|
116
|
-
};
|
|
117
|
-
|
|
118
56
|
/** Returns the offset for the progress view of the RefreshControl component
|
|
119
57
|
* based on navbar content position */
|
|
120
58
|
export const useGetProgressViewOffset = () => {
|
|
@@ -143,16 +81,12 @@ export const useGetProgressViewOffset = () => {
|
|
|
143
81
|
}
|
|
144
82
|
};
|
|
145
83
|
|
|
146
|
-
export function RefreshControl(props
|
|
147
|
-
pullToRefreshPipesV1RefreshingStateUpdater?: (refreshing: boolean) => void;
|
|
148
|
-
refreshingPipesV1?: boolean;
|
|
149
|
-
}) {
|
|
84
|
+
export function RefreshControl(props) {
|
|
150
85
|
const screenData = useCurrentScreenData();
|
|
151
86
|
|
|
152
87
|
const { refreshing, onRefresh } = usePullToRefresh(
|
|
153
|
-
screenData.
|
|
154
|
-
|
|
155
|
-
props.refreshingPipesV1
|
|
88
|
+
screenData.id,
|
|
89
|
+
screenData.ui_components
|
|
156
90
|
);
|
|
157
91
|
|
|
158
92
|
const { app_background_color: themeBackgroundColor } = useTheme();
|
|
@@ -170,29 +104,26 @@ export function RefreshControl(props: {
|
|
|
170
104
|
displayTitleIOS,
|
|
171
105
|
} = React.useMemo(
|
|
172
106
|
() => ({
|
|
173
|
-
indicatorColor:
|
|
174
|
-
|
|
175
|
-
screenData.styles
|
|
176
|
-
|
|
177
|
-
titleUnderIndicatorColor: R.prop(
|
|
178
|
-
"pull_to_refresh_title_color_under_indicator",
|
|
179
|
-
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"
|
|
180
111
|
),
|
|
181
|
-
indicatorBackgroundColor:
|
|
182
|
-
|
|
183
|
-
|
|
112
|
+
indicatorBackgroundColor: get(
|
|
113
|
+
screenData.styles,
|
|
114
|
+
"pull_to_refresh_indicator_bg_color"
|
|
184
115
|
),
|
|
185
116
|
indicatorSize:
|
|
186
|
-
|
|
117
|
+
get(screenData.styles, "pull_to_refresh_indicator_size") === "large"
|
|
187
118
|
? "large"
|
|
188
119
|
: "default",
|
|
189
|
-
generalContentBackgroungColor:
|
|
190
|
-
|
|
191
|
-
|
|
120
|
+
generalContentBackgroungColor: get(
|
|
121
|
+
screenData.styles,
|
|
122
|
+
"screen_background_color"
|
|
192
123
|
),
|
|
193
|
-
displayTitleIOS:
|
|
194
|
-
|
|
195
|
-
|
|
124
|
+
displayTitleIOS: get(
|
|
125
|
+
screenData.styles,
|
|
126
|
+
"pull_to_refresh_display_title_ios"
|
|
196
127
|
),
|
|
197
128
|
}),
|
|
198
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
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
|
|
55
|
-
|
|
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(
|
|
60
|
-
setScreenSummary(
|
|
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(
|
|
85
|
+
setScreenTitle(toStringOrEmpty(feedData.title));
|
|
68
86
|
}
|
|
69
87
|
|
|
70
88
|
if (feedData.summary && feedData.summary !== screenSummary) {
|
|
71
|
-
setScreenSummary(
|
|
89
|
+
setScreenSummary(toStringOrEmpty(feedData.summary));
|
|
72
90
|
}
|
|
73
91
|
} else {
|
|
74
|
-
setScreenTitle(
|
|
75
|
-
setScreenSummary(
|
|
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={
|
|
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 "
|
|
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,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
|
+
};
|
|
@@ -1,20 +1,9 @@
|
|
|
1
1
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
2
|
|
|
3
3
|
exports[`componentsMap renders renders components map correctly 1`] = `
|
|
4
|
-
<View
|
|
5
|
-
style={
|
|
6
|
-
{
|
|
7
|
-
"flex": 1,
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
>
|
|
4
|
+
<View>
|
|
11
5
|
<View
|
|
12
6
|
onLayout={[Function]}
|
|
13
|
-
style={
|
|
14
|
-
{
|
|
15
|
-
"flex": 1,
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
7
|
>
|
|
19
8
|
<RCTScrollView
|
|
20
9
|
ListFooterComponent={
|
|
@@ -155,6 +144,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
|
|
|
155
144
|
}
|
|
156
145
|
}
|
|
157
146
|
stickyHeaderIndices={[]}
|
|
147
|
+
testID="components-map-flat-list"
|
|
158
148
|
viewabilityConfigCallbackPairs={[]}
|
|
159
149
|
windowSize={12}
|
|
160
150
|
>
|
|
@@ -166,11 +156,6 @@ exports[`componentsMap renders renders components map correctly 1`] = `
|
|
|
166
156
|
>
|
|
167
157
|
<View
|
|
168
158
|
onLayout={[Function]}
|
|
169
|
-
style={
|
|
170
|
-
{
|
|
171
|
-
"flex": 1,
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
159
|
>
|
|
175
160
|
<View />
|
|
176
161
|
</View>
|
|
@@ -182,11 +167,6 @@ exports[`componentsMap renders renders components map correctly 1`] = `
|
|
|
182
167
|
>
|
|
183
168
|
<View
|
|
184
169
|
onLayout={[Function]}
|
|
185
|
-
style={
|
|
186
|
-
{
|
|
187
|
-
"flex": 1,
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
170
|
/>
|
|
191
171
|
</View>
|
|
192
172
|
<View
|
|
@@ -139,7 +139,13 @@ jest.mock(
|
|
|
139
139
|
})
|
|
140
140
|
);
|
|
141
141
|
|
|
142
|
+
jest.mock("@applicaster/zapp-react-native-ui-components/events", () => ({
|
|
143
|
+
...jest.requireActual("@applicaster/zapp-react-native-ui-components/events"),
|
|
144
|
+
emitScrollEndReached: jest.fn(),
|
|
145
|
+
}));
|
|
146
|
+
|
|
142
147
|
const { View } = require("react-native");
|
|
148
|
+
const events = require("@applicaster/zapp-react-native-ui-components/events");
|
|
143
149
|
const { ComponentsMap } = require("../ComponentsMap/ComponentsMap");
|
|
144
150
|
const theme = require("./theme-mock.json");
|
|
145
151
|
|
|
@@ -190,4 +196,36 @@ describe("componentsMap", () => {
|
|
|
190
196
|
|
|
191
197
|
expect(toJSON()).toMatchSnapshot();
|
|
192
198
|
});
|
|
199
|
+
|
|
200
|
+
it("calls emitScrollEndReached when onScroll was called and isScreenWrappedInContainer is false", () => {
|
|
201
|
+
themeSpy = jest
|
|
202
|
+
.spyOn(themeUtils, "useTheme")
|
|
203
|
+
.mockImplementation(() => () => theme);
|
|
204
|
+
|
|
205
|
+
events.emitScrollEndReached.mockClear();
|
|
206
|
+
|
|
207
|
+
const { getByTestId } = render(
|
|
208
|
+
<Provider store={store}>
|
|
209
|
+
<ComponentsMap
|
|
210
|
+
{...props}
|
|
211
|
+
isScreenWrappedInContainer={false}
|
|
212
|
+
feed={{ entry: [] }}
|
|
213
|
+
/>
|
|
214
|
+
</Provider>
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const flatList = getByTestId("components-map-flat-list");
|
|
218
|
+
|
|
219
|
+
flatList.props.onScroll({
|
|
220
|
+
nativeEvent: {
|
|
221
|
+
contentOffset: { y: 0 },
|
|
222
|
+
layoutMeasurement: { height: 100 },
|
|
223
|
+
contentSize: { height: 200 },
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
flatList.props.onEndReached();
|
|
228
|
+
|
|
229
|
+
expect(events.emitScrollEndReached).toHaveBeenCalledTimes(1);
|
|
230
|
+
});
|
|
193
231
|
});
|