@applicaster/zapp-react-native-ui-components 15.0.0-rc.12 → 15.0.0-rc.121
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/AnimatedInOut/index.tsx +69 -26
- package/Components/BaseFocusable/index.ios.ts +12 -2
- package/Components/Cell/Cell.tsx +14 -3
- package/Components/Cell/CellWithFocusable.tsx +9 -0
- package/Components/Cell/FocusableWrapper.tsx +47 -0
- package/Components/Cell/TvOSCellComponent.tsx +106 -19
- 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 +32 -1
- package/Components/GeneralContentScreen/GeneralContentScreen.tsx +39 -28
- package/Components/GeneralContentScreen/__tests__/GeneralContentScreen.test.tsx +104 -0
- package/Components/GeneralContentScreen/utils/__tests__/getScreenDataSource.test.ts +19 -0
- package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
- package/Components/GeneralContentScreen/utils/getScreenDataSource.ts +9 -0
- package/Components/GeneralContentScreen/utils/useCurationAPI.ts +22 -6
- package/Components/HandlePlayable/HandlePlayable.tsx +33 -94
- package/Components/HandlePlayable/const.ts +3 -0
- package/Components/HandlePlayable/utils.ts +105 -0
- package/Components/HookRenderer/HookRenderer.tsx +40 -10
- package/Components/HookRenderer/__tests__/HookRenderer.test.tsx +60 -0
- package/Components/Layout/TV/LayoutBackground.tsx +5 -2
- 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/index.tsx +3 -4
- package/Components/Layout/TV/index.web.tsx +3 -4
- package/Components/LinkHandler/LinkHandler.tsx +2 -2
- package/Components/MasterCell/DefaultComponents/BorderContainerView/__tests__/index.test.tsx +16 -1
- package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +30 -2
- package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +5 -1
- 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/__tests__/prepareEntry.test.ts +352 -0
- package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +136 -0
- package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +43 -22
- package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +40 -39
- package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/Image.test.tsx +95 -0
- package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +86 -0
- package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/index.test.ts +141 -0
- package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +7 -6
- package/Components/MasterCell/DefaultComponents/SecondaryImage/index.ts +1 -1
- package/Components/MasterCell/DefaultComponents/Text/index.tsx +8 -8
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +6 -2
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +233 -11
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +19 -15
- package/Components/MasterCell/hoc/__tests__/withAsyncRender.test.tsx +219 -0
- package/Components/MasterCell/hoc/withAsyncRender.tsx +9 -7
- package/Components/MasterCell/index.tsx +2 -0
- package/Components/MasterCell/utils/__tests__/resolveColor.test.js +82 -3
- package/Components/MasterCell/utils/index.ts +61 -31
- package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +102 -87
- package/Components/MeasurmentsPortal/__tests__/MeasurementsPortal.test.tsx +355 -0
- package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +17 -9
- package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +16 -8
- package/Components/OfflineHandler/NotificationView/NotificationView.tsx +2 -2
- package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +17 -18
- package/Components/OfflineHandler/NotificationView/utils.ts +34 -0
- package/Components/OfflineHandler/__tests__/index.test.tsx +27 -18
- package/Components/PlayerContainer/PlayerContainer.tsx +43 -64
- package/Components/PlayerImageBackground/index.tsx +3 -22
- package/Components/PreloaderWrapper/__tests__/index.test.tsx +26 -0
- package/Components/PreloaderWrapper/index.tsx +15 -0
- package/Components/River/ComponentsMap/ComponentsMap.tsx +16 -0
- package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
- package/Components/River/RefreshControl.tsx +9 -3
- package/Components/River/RiverItem.tsx +26 -20
- 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 -0
- package/Components/River/__tests__/componentsMap.test.js +38 -0
- package/Components/Screen/TV/index.web.tsx +4 -2
- package/Components/Screen/__tests__/Screen.test.tsx +66 -42
- package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +68 -44
- package/Components/Screen/hooks.ts +75 -6
- package/Components/Screen/index.tsx +9 -4
- package/Components/Screen/navigationHandler.ts +49 -24
- package/Components/Screen/orientationHandler.ts +10 -13
- package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +46 -0
- package/Components/ScreenFeedLoader/__tests__/ScreenFeedLoader.test.tsx +94 -0
- package/Components/ScreenFeedLoader/index.ts +1 -0
- package/Components/ScreenResolver/__tests__/screenResolver.test.js +24 -0
- package/Components/ScreenResolver/hooks/index.ts +3 -0
- package/Components/ScreenResolver/hooks/useGetComponent.ts +15 -0
- package/Components/ScreenResolver/hooks/useScreenComponentResolver.tsx +90 -0
- package/Components/ScreenResolver/index.tsx +15 -111
- package/Components/ScreenResolver/utils/__tests__/getScreenTypeProps.test.ts +45 -0
- package/Components/ScreenResolver/utils/getScreenTypeProps.ts +43 -0
- package/Components/ScreenResolver/utils/index.ts +1 -0
- package/Components/ScreenResolver/withDefaultScreenContext.tsx +16 -0
- package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +25 -0
- package/Components/ScreenResolverFeedProvider/__tests__/ScreenResolverFeedProvider.test.tsx +44 -0
- package/Components/ScreenResolverFeedProvider/index.ts +1 -0
- package/Components/ScreenRevealManager/ScreenRevealManager.ts +40 -8
- package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +86 -69
- package/Components/ScreenRevealManager/withScreenRevealManager.tsx +44 -26
- package/Components/Tabs/TV/Tabs.tsx +20 -3
- package/Components/Tabs/TabContent.tsx +7 -4
- package/Components/Transitioner/Scene.tsx +10 -3
- package/Components/Transitioner/index.js +3 -3
- package/Components/VideoLive/LiveImageManager.ts +199 -54
- package/Components/VideoLive/PlayerLiveImageComponent.tsx +31 -33
- package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +2 -17
- package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +1 -0
- package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +118 -171
- package/Components/VideoModal/ModalAnimation/index.ts +2 -13
- package/Components/VideoModal/ModalAnimation/utils.ts +1 -327
- package/Components/VideoModal/PlayerWrapper.tsx +14 -88
- package/Components/VideoModal/VideoModal.tsx +1 -5
- package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -0
- package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +15 -7
- package/Components/VideoModal/hooks/useModalSize.ts +10 -5
- package/Components/VideoModal/playerWrapperStyle.ts +70 -0
- package/Components/VideoModal/playerWrapperUtils.ts +91 -0
- package/Components/VideoModal/utils.ts +19 -9
- 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/ZappUIComponent/index.tsx +12 -6
- package/Components/index.js +1 -1
- package/Contexts/ScreenContext/__tests__/index.test.tsx +57 -0
- package/Contexts/ScreenContext/index.tsx +71 -19
- package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
- package/Contexts/ZappHookModalContext/index.tsx +37 -61
- package/Contexts/ZappPipesContext/ZappPipesContextFactory.tsx +18 -7
- package/Contexts/index.ts +0 -2
- 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/ResolverSelector.tsx +25 -7
- package/Decorators/ZappPipesDataConnector/__tests__/ResolverSelector.test.tsx +212 -5
- 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/events/index.ts +3 -0
- package/events/scrollEndReached.ts +15 -0
- package/index.d.ts +7 -0
- package/package.json +6 -5
- package/Components/PlayerContainer/ErrorDisplay/ErrorDisplay.tsx +0 -57
- package/Components/PlayerContainer/ErrorDisplay/index.ts +0 -9
- package/Components/River/TV/withTVEventHandler.tsx +0 -27
- package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +0 -60
- package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +0 -417
- package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.web.tsx +0 -294
- package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +0 -176
- package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.web.tsx +0 -93
- package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +0 -500
- package/Components/VideoModal/ModalAnimation/__tests__/getMoveUpValue.test.ts +0 -108
- package/Helpers/DataSourceHelper/index.js +0 -19
- /package/Components/HookRenderer/{index.tsx → index.ts} +0 -0
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
|
|
18
18
|
import { TVEventHandlerComponent } from "@applicaster/zapp-react-native-tvos-ui-components/Components/TVEventHandlerComponent";
|
|
19
19
|
import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
import {
|
|
22
22
|
useBackHandler,
|
|
23
23
|
useNavigation,
|
|
@@ -46,7 +46,6 @@ import {
|
|
|
46
46
|
PlayerContainerContextProvider,
|
|
47
47
|
} from "./PlayerContainerContext";
|
|
48
48
|
import { FocusableGroup } from "@applicaster/zapp-react-native-ui-components/Components/FocusableGroup";
|
|
49
|
-
import { ErrorDisplay } from "./ErrorDisplay";
|
|
50
49
|
import { PlayerFocusableWrapperView } from "./WappersView/PlayerFocusableWrapperView";
|
|
51
50
|
import { FocusableGroupMainContainerId } from "./index";
|
|
52
51
|
import { isPlayable } from "@applicaster/zapp-react-native-utils/navigationUtils/itemTypeMatchers";
|
|
@@ -56,15 +55,11 @@ import { toNumber } from "@applicaster/zapp-react-native-utils/numberUtils";
|
|
|
56
55
|
import { usePlayNextOverlay } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayNextOverlay";
|
|
57
56
|
import { PlayNextState } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/OverlayObserver/OverlaysObserver";
|
|
58
57
|
|
|
59
|
-
import {
|
|
60
|
-
PlayerAnimationStateEnum,
|
|
61
|
-
useModalAnimationContext,
|
|
62
|
-
} from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
|
|
63
|
-
|
|
64
58
|
import {
|
|
65
59
|
PlayerNativeCommandTypes,
|
|
66
60
|
PlayerNativeSendCommand,
|
|
67
61
|
} from "@applicaster/zapp-react-native-utils/appUtils/playerManager/playerNativeCommand";
|
|
62
|
+
import { useAppData } from "@applicaster/zapp-react-native-redux";
|
|
68
63
|
|
|
69
64
|
type Props = {
|
|
70
65
|
Player: React.ComponentType<any>;
|
|
@@ -243,14 +238,11 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
243
238
|
const [isLoadingNextVideo, setIsLoadingNextVideo] = React.useState(false);
|
|
244
239
|
|
|
245
240
|
const navigator = useNavigation();
|
|
246
|
-
const {
|
|
241
|
+
const { isTabletPortrait } = useAppData();
|
|
247
242
|
const prevItemId = usePrevious(item?.id);
|
|
248
243
|
const screenData = useTargetScreenData(item);
|
|
249
244
|
const { setVisible: showNavBar } = useSetNavbarState();
|
|
250
245
|
|
|
251
|
-
const { isActiveGesture, startComponentsAnimation, setPlayerAnimationState } =
|
|
252
|
-
useModalAnimationContext();
|
|
253
|
-
|
|
254
246
|
const playerEvent = (event, ...args) => {
|
|
255
247
|
playerManager.invokeHandler(event, ...args);
|
|
256
248
|
};
|
|
@@ -271,7 +263,14 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
271
263
|
|
|
272
264
|
showNavBar(true);
|
|
273
265
|
navigator.goBack();
|
|
274
|
-
}, [isModal,
|
|
266
|
+
}, [isModal, state.playerId, showNavBar, navigator]);
|
|
267
|
+
|
|
268
|
+
const pluginConfiguration = React.useMemo(() => {
|
|
269
|
+
return (
|
|
270
|
+
playerManager.getPluginConfiguration() ||
|
|
271
|
+
R.prop("__plugin_configuration", Player)
|
|
272
|
+
);
|
|
273
|
+
}, [playerManager.isRegistered()]);
|
|
275
274
|
|
|
276
275
|
const playEntry = (entry) => navigator.replaceTop(entry, { mode });
|
|
277
276
|
|
|
@@ -339,12 +338,6 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
339
338
|
playerContainerLogger.error(errorObj);
|
|
340
339
|
|
|
341
340
|
setState({ error: errorObj });
|
|
342
|
-
|
|
343
|
-
if (!isTvOS) {
|
|
344
|
-
setTimeout(() => {
|
|
345
|
-
close();
|
|
346
|
-
}, 800);
|
|
347
|
-
}
|
|
348
341
|
},
|
|
349
342
|
[close]
|
|
350
343
|
);
|
|
@@ -463,13 +456,6 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
463
456
|
}
|
|
464
457
|
}, []);
|
|
465
458
|
|
|
466
|
-
const pluginConfiguration = React.useMemo(() => {
|
|
467
|
-
return (
|
|
468
|
-
playerManager.getPluginConfiguration() ||
|
|
469
|
-
R.prop("__plugin_configuration", Player)
|
|
470
|
-
);
|
|
471
|
-
}, [playerManager.isRegistered()]);
|
|
472
|
-
|
|
473
459
|
const disableMiniPlayer = React.useMemo(() => {
|
|
474
460
|
return pluginConfiguration?.disable_mini_player_when_inline;
|
|
475
461
|
}, [pluginConfiguration]);
|
|
@@ -482,8 +468,6 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
482
468
|
if (isModal && mode === VideoModalMode.MAXIMIZED) {
|
|
483
469
|
if (disableMiniPlayer) {
|
|
484
470
|
navigator.closeVideoModal();
|
|
485
|
-
} else {
|
|
486
|
-
setPlayerAnimationState(PlayerAnimationStateEnum.minimize);
|
|
487
471
|
}
|
|
488
472
|
}
|
|
489
473
|
|
|
@@ -671,44 +655,39 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
671
655
|
<PlayerFocusableWrapperView
|
|
672
656
|
nextFocusDown={context.bottomFocusableId}
|
|
673
657
|
>
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
navigator.isVideoModalDocked()
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
>
|
|
707
|
-
{renderApplePlayer(applePlayerProps)}
|
|
708
|
-
</Player>
|
|
658
|
+
{!Player ? null : (
|
|
659
|
+
<Player
|
|
660
|
+
source={{
|
|
661
|
+
uri,
|
|
662
|
+
entry: item,
|
|
663
|
+
}}
|
|
664
|
+
focused={isInlineTV ? true : undefined}
|
|
665
|
+
autoplay={true}
|
|
666
|
+
controls={false}
|
|
667
|
+
disableCastAction={disableCastAction}
|
|
668
|
+
docked={navigator.isVideoModalDocked()}
|
|
669
|
+
entry={item}
|
|
670
|
+
fullscreen={mode === VideoModalMode.FULLSCREEN}
|
|
671
|
+
inline={inline}
|
|
672
|
+
isModal={isModal}
|
|
673
|
+
isTabletPortrait={isTabletPortrait}
|
|
674
|
+
muted={false}
|
|
675
|
+
playableItem={item}
|
|
676
|
+
playerEvent={playerEvent}
|
|
677
|
+
playerId={state.playerId}
|
|
678
|
+
pluginConfiguration={pluginConfiguration}
|
|
679
|
+
ref={playerRef}
|
|
680
|
+
toggleFullscreen={toggleFullscreen}
|
|
681
|
+
style={videoStyle}
|
|
682
|
+
playNextData={playNextData}
|
|
683
|
+
setNextVideoPreloadThresholdPercentage={
|
|
684
|
+
setNextVideoPreloadThresholdPercentage
|
|
685
|
+
}
|
|
686
|
+
>
|
|
687
|
+
{renderApplePlayer(applePlayerProps)}
|
|
688
|
+
</Player>
|
|
689
|
+
)}
|
|
709
690
|
</PlayerFocusableWrapperView>
|
|
710
|
-
|
|
711
|
-
{state.error ? <ErrorDisplay error={state.error} /> : null}
|
|
712
691
|
</View>
|
|
713
692
|
{/* Components container */}
|
|
714
693
|
{isInlineTV && context.showComponentsContainer ? (
|
|
@@ -2,12 +2,6 @@ import React, { PropsWithChildren } from "react";
|
|
|
2
2
|
import { ImageBackground, View } from "react-native";
|
|
3
3
|
|
|
4
4
|
import { imageSrcFromMediaItem } from "@applicaster/zapp-react-native-utils/configurationUtils";
|
|
5
|
-
import {
|
|
6
|
-
AnimationComponent,
|
|
7
|
-
ComponentAnimationType,
|
|
8
|
-
useModalAnimationContext,
|
|
9
|
-
PlayerAnimationStateEnum,
|
|
10
|
-
} from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
|
|
11
5
|
|
|
12
6
|
type Props = PropsWithChildren<{
|
|
13
7
|
entry: ZappEntry;
|
|
@@ -25,30 +19,17 @@ const PlayerImageBackgroundComponent = ({
|
|
|
25
19
|
style,
|
|
26
20
|
imageStyle,
|
|
27
21
|
imageKey,
|
|
28
|
-
defaultImageDimensions,
|
|
29
22
|
}: Props) => {
|
|
30
23
|
const source = React.useMemo(
|
|
31
24
|
() => ({ uri: imageSrcFromMediaItem(entry, [imageKey]) }),
|
|
32
25
|
[imageKey, entry]
|
|
33
26
|
);
|
|
34
27
|
|
|
35
|
-
const { playerAnimationState } = useModalAnimationContext();
|
|
36
|
-
|
|
37
28
|
if (!source) return <>{children}</>;
|
|
38
29
|
|
|
39
30
|
return (
|
|
40
|
-
<View
|
|
41
|
-
style={
|
|
42
|
-
playerAnimationState === PlayerAnimationStateEnum.maximize
|
|
43
|
-
? defaultImageDimensions
|
|
44
|
-
: style
|
|
45
|
-
}
|
|
46
|
-
>
|
|
47
|
-
<AnimationComponent
|
|
48
|
-
style={style}
|
|
49
|
-
animationType={ComponentAnimationType.player}
|
|
50
|
-
additionalData={defaultImageDimensions}
|
|
51
|
-
>
|
|
31
|
+
<View style={style}>
|
|
32
|
+
<View style={style}>
|
|
52
33
|
<ImageBackground
|
|
53
34
|
resizeMode="cover"
|
|
54
35
|
style={imageSize}
|
|
@@ -57,7 +38,7 @@ const PlayerImageBackgroundComponent = ({
|
|
|
57
38
|
>
|
|
58
39
|
{children}
|
|
59
40
|
</ImageBackground>
|
|
60
|
-
</
|
|
41
|
+
</View>
|
|
61
42
|
</View>
|
|
62
43
|
);
|
|
63
44
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Text } from "react-native";
|
|
3
|
+
import { render } from "@testing-library/react-native";
|
|
4
|
+
import { PreloaderWrapper } from "..";
|
|
5
|
+
|
|
6
|
+
describe("PreloaderWrapper", () => {
|
|
7
|
+
it("renders children when preloader is hidden", () => {
|
|
8
|
+
const { getByText } = render(
|
|
9
|
+
<PreloaderWrapper showPreloader={false}>
|
|
10
|
+
<Text>content</Text>
|
|
11
|
+
</PreloaderWrapper>
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
expect(getByText("content")).toBeDefined();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("renders null when preloader is shown", () => {
|
|
18
|
+
const { queryByText } = render(
|
|
19
|
+
<PreloaderWrapper showPreloader>
|
|
20
|
+
<Text>content</Text>
|
|
21
|
+
</PreloaderWrapper>
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
expect(queryByText("content")).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
type PreloaderWrapperProps = {
|
|
4
|
+
showPreloader?: boolean;
|
|
5
|
+
children?: React.ReactNode;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const PreloaderWrapper: React.FC<PreloaderWrapperProps> = ({
|
|
9
|
+
showPreloader = false,
|
|
10
|
+
children,
|
|
11
|
+
}) => {
|
|
12
|
+
return !showPreloader ? children : null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default PreloaderWrapper;
|
|
@@ -23,6 +23,7 @@ import { isLast } from "@applicaster/zapp-react-native-utils/arrayUtils";
|
|
|
23
23
|
import { withComponentsMapProvider } from "@applicaster/zapp-react-native-ui-components/Decorators/ComponentsMapWrapper";
|
|
24
24
|
import { useScreenContextV2 } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
|
|
25
25
|
import { useShallow } from "zustand/react/shallow";
|
|
26
|
+
import { emitScrollEndReached } from "@applicaster/zapp-react-native-ui-components/events";
|
|
26
27
|
|
|
27
28
|
import { isAndroidPlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
28
29
|
import { ComponentsMapHeightContext } from "./ContextProviders/ComponentsMapHeightContext";
|
|
@@ -73,6 +74,7 @@ function ComponentsMapComponent(props: Props) {
|
|
|
73
74
|
|
|
74
75
|
const flatListRef = React.useRef<FlatList | null>(null);
|
|
75
76
|
const flatListWrapperRef = React.useRef<View | null>(null);
|
|
77
|
+
const hasUserScrolledRef = React.useRef(false);
|
|
76
78
|
const screenConfig = useScreenConfiguration(riverId);
|
|
77
79
|
const screenData = useScreenData(riverId);
|
|
78
80
|
const pullToRefreshEnabled = screenData?.rules?.pull_to_refresh_enabled;
|
|
@@ -236,6 +238,8 @@ function ComponentsMapComponent(props: Props) {
|
|
|
236
238
|
}, []);
|
|
237
239
|
|
|
238
240
|
const onScroll = React.useCallback((event) => {
|
|
241
|
+
hasUserScrolledRef.current = true;
|
|
242
|
+
|
|
239
243
|
const {
|
|
240
244
|
nativeEvent: {
|
|
241
245
|
contentOffset: { y },
|
|
@@ -277,6 +281,7 @@ function ComponentsMapComponent(props: Props) {
|
|
|
277
281
|
>
|
|
278
282
|
<ViewportTracker>
|
|
279
283
|
<FlatList
|
|
284
|
+
testID="components-map-flat-list"
|
|
280
285
|
ref={(ref) => {
|
|
281
286
|
flatListRef.current = ref;
|
|
282
287
|
}}
|
|
@@ -308,6 +313,17 @@ function ComponentsMapComponent(props: Props) {
|
|
|
308
313
|
onScrollEndDrag={_onScrollEndDrag}
|
|
309
314
|
scrollEventThrottle={16}
|
|
310
315
|
{...scrollViewExtraProps}
|
|
316
|
+
onEndReached={
|
|
317
|
+
/* When wrapped in a parent ScrollView (e.g. tabs),
|
|
318
|
+
this FlatList doesn't scroll so onEndReached can fire repeatedly;
|
|
319
|
+
skip it here and let the parent ScrollView emit scroll-end instead. */
|
|
320
|
+
isScreenWrappedInContainer
|
|
321
|
+
? undefined
|
|
322
|
+
: () => {
|
|
323
|
+
if (!hasUserScrolledRef.current) return;
|
|
324
|
+
emitScrollEndReached();
|
|
325
|
+
}
|
|
326
|
+
}
|
|
311
327
|
/>
|
|
312
328
|
</ViewportTracker>
|
|
313
329
|
</ScreenLoadingMeasurements>
|
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
StyleSheet,
|
|
6
6
|
} from "react-native";
|
|
7
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";
|
|
8
10
|
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
9
11
|
import { useLocalizedStrings } from "@applicaster/zapp-react-native-utils/localizationUtils";
|
|
10
12
|
import { useAnalytics } from "@applicaster/zapp-react-native-utils/analyticsUtils";
|
|
@@ -62,9 +64,13 @@ export const usePullToRefresh = (
|
|
|
62
64
|
|
|
63
65
|
const [refreshing, setRefreshing] = React.useState(false);
|
|
64
66
|
|
|
65
|
-
const feeds: string[] =
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
const feeds: string[] = React.useMemo(
|
|
68
|
+
() =>
|
|
69
|
+
(riverComponents || [])
|
|
70
|
+
.map((riverComponent) => path(["data", "source"], riverComponent))
|
|
71
|
+
.filter((feed) => !isNilOrEmpty(feed)),
|
|
72
|
+
[riverComponents]
|
|
73
|
+
);
|
|
68
74
|
|
|
69
75
|
const feedsLength = feeds.length;
|
|
70
76
|
|
|
@@ -14,6 +14,7 @@ import { tvPluginsWithCellRenderer } from "../../const";
|
|
|
14
14
|
import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
15
15
|
import type { BehaviorSubject } from "rxjs";
|
|
16
16
|
import { useCallbackActions } from "@applicaster/zapp-react-native-utils/zappFrameworkUtils/HookCallback/useCallbackActions";
|
|
17
|
+
import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
17
18
|
|
|
18
19
|
export type RiverItemType = {
|
|
19
20
|
item: ZappUIComponent;
|
|
@@ -112,33 +113,38 @@ function RiverItemComponent(props: RiverItemType) {
|
|
|
112
113
|
CellRenderer = undefined;
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
|
|
116
|
-
riverLogger.log({
|
|
117
|
-
message: "mounting component",
|
|
118
|
-
data: { item, feedUrl, Component, CellRenderer },
|
|
119
|
-
jsOnly: true,
|
|
120
|
-
});
|
|
116
|
+
const isComponentMissing = isNilOrEmpty(Component);
|
|
121
117
|
|
|
122
|
-
|
|
118
|
+
/**
|
|
119
|
+
* TODO: Move this plugin existence check further up the stack (before ComponentsMap).
|
|
120
|
+
* Filtering items at the list-rendering or data-processing level would prevent
|
|
121
|
+
* mounting RiverItem entirely for missing components.
|
|
122
|
+
*/
|
|
123
|
+
React.useEffect(() => {
|
|
124
|
+
if (isComponentMissing) {
|
|
123
125
|
riverLogger.warning({
|
|
124
|
-
message:
|
|
125
|
-
|
|
126
|
+
message: `Component ${item.component_type} is null - skipping rendering`,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
onLoadFinished(index);
|
|
130
|
+
} else {
|
|
131
|
+
riverLogger.log({
|
|
132
|
+
message: "mounting component",
|
|
133
|
+
data: { item, feedUrl, Component, CellRenderer },
|
|
126
134
|
jsOnly: true,
|
|
127
135
|
});
|
|
136
|
+
|
|
137
|
+
if (!CellRenderer && !isGroup(item)) {
|
|
138
|
+
riverLogger.warning({
|
|
139
|
+
message: "Cell Renderer is null - will fallback to default cell",
|
|
140
|
+
data: { item, CellRenderer },
|
|
141
|
+
jsOnly: true,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
128
144
|
}
|
|
129
145
|
}, []);
|
|
130
146
|
|
|
131
|
-
if (!readyToBeDisplayed) {
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (Component === null || typeof Component === "undefined") {
|
|
136
|
-
riverLogger.warning({
|
|
137
|
-
message: `Component ${item.component_type} is null - skipping rendering`,
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
onLoadFinished(index);
|
|
141
|
-
|
|
147
|
+
if (!readyToBeDisplayed || isComponentMissing) {
|
|
142
148
|
return null;
|
|
143
149
|
}
|
|
144
150
|
|
|
@@ -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
|
+
};
|