@applicaster/zapp-react-native-ui-components 11.0.0-rc.99 → 12.0.0-rc.1
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/BaseFocusable/index.ios.ts +1 -1
- package/Components/Cell/Cell.tsx +5 -13
- package/Components/Cell/{CellWithFocusable.js → CellWithFocusable.tsx} +13 -11
- package/Components/Cell/TvOSCellComponent.tsx +12 -10
- package/Components/FastImage/index.ios.js +3 -0
- package/Components/Focusable/FocusableTvOS.tsx +4 -1
- package/Components/FocusableCell/{index.js → index.tsx} +8 -5
- package/Components/FocusableGroup/FocusableTvOS.tsx +2 -2
- package/Components/GeneralContentScreen/utils/useCurationAPI.ts +13 -8
- package/Components/HandlePlayable/HandlePlayable.tsx +2 -2
- package/Components/HookRenderer/HookRenderer.tsx +9 -6
- package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +1 -1
- package/Components/Layout/TV/index.tsx +9 -13
- package/Components/Layout/index.tsx +9 -1
- package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +3 -1
- package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +22 -19
- package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +1 -0
- package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/Image.test.tsx +4 -1
- package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +2 -0
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/CollapsibleTextContainer.tsx +9 -3
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/__tests__/__snapshots__/index.test.tsx.snap +7 -1
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/__tests__/index.test.tsx +12 -4
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/index.ts +23 -9
- package/Components/MasterCell/hoc/withAsyncRender.tsx +8 -4
- package/Components/MasterCell/hooks/useAsyncRendering/MasterCellAsyncRenderManager.ts +12 -6
- package/Components/MasterCell/hooks/useAsyncRendering/__tests__/MasterCellAsyncRenderManager.test.ts +21 -0
- package/Components/MasterCell/hooks/useAsyncRendering/index.ts +15 -13
- package/Components/MasterCell/utils/index.ts +143 -19
- package/Components/PlayerContainer/PlayerContainer.tsx +10 -6
- package/Components/River/ComponentsMap/ComponentsMap.tsx +4 -1
- package/Components/River/River.tsx +3 -4
- package/Components/River/TV/River.tsx +9 -4
- package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +1 -0
- package/Components/River/index.tsx +3 -1
- package/Components/Screen/__tests__/Screen.test.tsx +6 -3
- package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +4 -0
- package/Components/Screen/index.tsx +12 -44
- package/Components/Screen/navigationHandler.ts +1 -1
- package/Components/Spinner/SpinnerComponent.tsx +6 -2
- package/Components/VideoLive/LiveImageManager.ts +24 -9
- package/Components/VideoLive/PlayerLiveImageComponent.tsx +23 -1
- package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +74 -0
- package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +47 -0
- package/Components/VideoLive/__tests__/utils.test.ts +109 -0
- package/Components/VideoLive/utils.ts +40 -0
- package/Components/VideoModal/PlayerDetails.tsx +42 -6
- package/Components/VideoModal/PlayerWrapper.tsx +32 -29
- package/Components/VideoModal/VideoModal.tsx +20 -17
- package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +21 -9
- package/Components/VideoModal/__tests__/__snapshots__/PlayerDetails.test.tsx.snap +14 -24
- package/Contexts/InteractionManagerContext/index.ts +0 -2
- package/Contexts/ScreenContext/index.tsx +118 -0
- package/package.json +5 -5
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
/// <reference types="@applicaster/applicaster-types" />
|
|
2
2
|
import React, { useMemo } from "react";
|
|
3
3
|
import * as R from "ramda";
|
|
4
|
-
import {
|
|
4
|
+
import { View } from "react-native";
|
|
5
5
|
import { RouteManager } from "../RouteManager";
|
|
6
6
|
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
7
|
-
import {
|
|
8
|
-
useNavBarStateForScreen,
|
|
9
|
-
useNavigation,
|
|
10
|
-
} from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
|
|
11
7
|
import { useScreenOrientationHandler } from "./orientationHandler";
|
|
12
|
-
import { isNavBarVisible } from "./navigationHandler";
|
|
13
8
|
import { useScreenConfiguration } from "../River/useScreenConfiguration";
|
|
14
9
|
import { useCurrentScreenData } from "@applicaster/zapp-react-native-utils/reactHooks/screen";
|
|
15
10
|
import type { ZappNavigationProps } from "@applicaster/zapp-react-native-flow-types/index";
|
|
16
11
|
import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
|
|
12
|
+
import { ScreenContextProvider } from "../../Contexts/ScreenContext";
|
|
17
13
|
|
|
18
14
|
type Props = {
|
|
19
15
|
pathname: string;
|
|
@@ -71,12 +67,8 @@ export function Screen(props: Props) {
|
|
|
71
67
|
|
|
72
68
|
// Gets the data for the current screen configuration
|
|
73
69
|
const currentScreenData = useCurrentScreenData();
|
|
74
|
-
|
|
75
70
|
const theme = useTheme();
|
|
76
|
-
|
|
77
|
-
const navigator = useNavigation();
|
|
78
|
-
const screenConfig = useScreenConfiguration(currentScreenData.id);
|
|
79
|
-
const { backgroundColor } = useMemo(() => screenConfig, [pathname]);
|
|
71
|
+
const { backgroundColor } = useScreenConfiguration(currentScreenData.id);
|
|
80
72
|
|
|
81
73
|
const style = React.useMemo(
|
|
82
74
|
() => ({
|
|
@@ -88,44 +80,20 @@ export function Screen(props: Props) {
|
|
|
88
80
|
[theme.app_background_color, backgroundColor]
|
|
89
81
|
);
|
|
90
82
|
|
|
91
|
-
const { isNavBarVisible: showNavBar, canGoBack } =
|
|
92
|
-
useNavBarStateForScreen(pathname);
|
|
93
|
-
|
|
94
|
-
const navBarVisible = useMemo(
|
|
95
|
-
() =>
|
|
96
|
-
isNavBarVisible(
|
|
97
|
-
pathname,
|
|
98
|
-
currentScreenData || screenData,
|
|
99
|
-
showNavBar,
|
|
100
|
-
canGoBack(),
|
|
101
|
-
navigator.videoModalState
|
|
102
|
-
),
|
|
103
|
-
[pathname, showNavBar, navigator.videoModalState]
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
const navBarProps = useMemo(() => props.navBarProps, []);
|
|
107
|
-
|
|
108
83
|
const targetScreenData =
|
|
109
84
|
currentScreenData || screenData?.targetScreen || screenData;
|
|
110
85
|
|
|
111
86
|
useScreenOrientationHandler({ screenData: targetScreenData });
|
|
112
87
|
|
|
113
|
-
const shouldHideStatusBar = useMemo(
|
|
114
|
-
() =>
|
|
115
|
-
!navBarVisible &&
|
|
116
|
-
navigator.videoModalState?.mode === "FULLSCREEN" &&
|
|
117
|
-
navigator.videoModalState?.visible === true,
|
|
118
|
-
[navBarVisible, navigator.videoModalState]
|
|
119
|
-
);
|
|
120
|
-
|
|
121
88
|
return (
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
89
|
+
<ScreenContextProvider pathname={pathname}>
|
|
90
|
+
<View style={style}>
|
|
91
|
+
<NavBar key={pathname} {...props.navBarProps} />
|
|
92
|
+
|
|
93
|
+
<OfflineFallbackScreen screenData={screenData}>
|
|
94
|
+
<RouteManager pathname={pathname} screenData={screenData} />
|
|
95
|
+
</OfflineFallbackScreen>
|
|
96
|
+
</View>
|
|
97
|
+
</ScreenContextProvider>
|
|
130
98
|
);
|
|
131
99
|
}
|
|
@@ -8,11 +8,15 @@ type ActivityIndicatorThemeValues = {
|
|
|
8
8
|
loading_spinner_color: ActivityIndicatorProps["color"];
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
type Props = ActivityIndicatorProps & { showAnyway?: boolean };
|
|
12
|
+
|
|
13
|
+
export function SpinnerComponent(props: Props) {
|
|
12
14
|
const { loading_spinner_size, loading_spinner_color } =
|
|
13
15
|
useTheme() as ActivityIndicatorThemeValues;
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
const { showAnyway = false } = props;
|
|
18
|
+
|
|
19
|
+
if (isAndroidTVPlatform() && !showAnyway) {
|
|
16
20
|
return null;
|
|
17
21
|
}
|
|
18
22
|
|
|
@@ -351,9 +351,11 @@ export class LiveImageManager implements PlayerLifecycleListener {
|
|
|
351
351
|
}, primaryPlayerId: ${this.primaryPlayer?.playerId}`
|
|
352
352
|
);
|
|
353
353
|
|
|
354
|
+
const isCurrentItemEnded = this.currentlyPlaying === item;
|
|
355
|
+
|
|
354
356
|
// TODO: This branch was used when we had play button to toggle replay.
|
|
355
357
|
// Now we mute button instead but we keep it just in case.
|
|
356
|
-
if (IS_ALLOWED_REPLAY && !isTV() &&
|
|
358
|
+
if (IS_ALLOWED_REPLAY && !isTV() && isCurrentItemEnded) {
|
|
357
359
|
this.currentlyPlaying.player.seekTo(0);
|
|
358
360
|
this.currentlyPlaying.player.pause();
|
|
359
361
|
|
|
@@ -362,14 +364,16 @@ export class LiveImageManager implements PlayerLifecycleListener {
|
|
|
362
364
|
item.setMode(LiveImageType.Image);
|
|
363
365
|
}
|
|
364
366
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
367
|
+
// Prevent receiving onEnd event from both `close` and `end` player events
|
|
368
|
+
isCurrentItemEnded &&
|
|
369
|
+
this.invokeListenersUpdate({
|
|
370
|
+
callbackName: "onEnd",
|
|
371
|
+
event: {
|
|
372
|
+
item,
|
|
373
|
+
playerId: this.currentlyPlaying?.playerId,
|
|
374
|
+
primaryPlayerId: this.primaryPlayer?.playerId,
|
|
375
|
+
},
|
|
376
|
+
});
|
|
373
377
|
};
|
|
374
378
|
|
|
375
379
|
onLiveImageError = (item: LiveImage, error: Error) => {
|
|
@@ -484,4 +488,15 @@ export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
|
|
|
484
488
|
|
|
485
489
|
LiveImageManager.instance.onLiveImageEnded(this);
|
|
486
490
|
};
|
|
491
|
+
|
|
492
|
+
onPlayerClose = () => {
|
|
493
|
+
log_info(
|
|
494
|
+
`onPlayerClose: live-image, player closed, switching back to image, ${playerInfo(
|
|
495
|
+
this.player
|
|
496
|
+
)}`
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
// TODO: we maybe want to add onPlayerClose to separate completion behavior
|
|
500
|
+
LiveImageManager.instance.onLiveImageEnded(this);
|
|
501
|
+
};
|
|
487
502
|
}
|
|
@@ -24,6 +24,7 @@ import { overlayFadeIn } from "./animationUtils";
|
|
|
24
24
|
import { loggerLiveImageComponent } from "./loggerHelper";
|
|
25
25
|
import { usePlayer } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer";
|
|
26
26
|
import { PlayerRole } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/conts";
|
|
27
|
+
import { getAutoplaySettings } from "./utils";
|
|
27
28
|
|
|
28
29
|
const { log_error } = loggerLiveImageComponent;
|
|
29
30
|
|
|
@@ -114,6 +115,11 @@ const PlayerLiveImageComponent = (props: Props) => {
|
|
|
114
115
|
}
|
|
115
116
|
}, [playerId, item?.id]);
|
|
116
117
|
|
|
118
|
+
const { start, end } = React.useMemo(
|
|
119
|
+
() => getAutoplaySettings(item),
|
|
120
|
+
[item?.id]
|
|
121
|
+
);
|
|
122
|
+
|
|
117
123
|
const controller = liveImageItem.getPlayer();
|
|
118
124
|
const player = usePlayer(playerId);
|
|
119
125
|
|
|
@@ -121,6 +127,10 @@ const PlayerLiveImageComponent = (props: Props) => {
|
|
|
121
127
|
ref.current = component;
|
|
122
128
|
};
|
|
123
129
|
|
|
130
|
+
const onStop = React.useCallback(() => {
|
|
131
|
+
LiveImageManager.instance.pauseLiveImage(liveImageItem);
|
|
132
|
+
}, [liveImageItem]);
|
|
133
|
+
|
|
124
134
|
React.useEffect(() => {
|
|
125
135
|
// FIXME - find a more elegant way to disable live-image on cell for measurement
|
|
126
136
|
if (isMeasurement(item)) {
|
|
@@ -147,8 +157,20 @@ const PlayerLiveImageComponent = (props: Props) => {
|
|
|
147
157
|
id: "mute-action-button",
|
|
148
158
|
listener: {
|
|
149
159
|
onLoad: () => {
|
|
160
|
+
start && player.seekTo(start);
|
|
150
161
|
setIsReady(true);
|
|
151
162
|
},
|
|
163
|
+
onVideoProgress: ({ currentTime }) => {
|
|
164
|
+
if (start && currentTime < start) {
|
|
165
|
+
// fixes the case when the video is not seekable in onLoad callback
|
|
166
|
+
player.seekTo(start);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (end && currentTime > end) {
|
|
170
|
+
onStop();
|
|
171
|
+
setIsReady(false);
|
|
172
|
+
}
|
|
173
|
+
},
|
|
152
174
|
onPlayerMute: () => {
|
|
153
175
|
setMuted(true);
|
|
154
176
|
},
|
|
@@ -160,7 +182,7 @@ const PlayerLiveImageComponent = (props: Props) => {
|
|
|
160
182
|
},
|
|
161
183
|
},
|
|
162
184
|
});
|
|
163
|
-
}, [player]);
|
|
185
|
+
}, [player, onStop]);
|
|
164
186
|
|
|
165
187
|
React.useEffect(() => {
|
|
166
188
|
const subscription = AppState.addEventListener(
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/* eslint-disable react/display-name */
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { render } from "@testing-library/react-native";
|
|
4
|
+
import * as PlayerFactory from "@applicaster/zapp-react-native-utils/appUtils/playerManager/playerFactory";
|
|
5
|
+
import { Player } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/player";
|
|
6
|
+
import { PlayerLiveImage } from "../PlayerLiveImageComponent";
|
|
7
|
+
|
|
8
|
+
const seekTo = jest.fn();
|
|
9
|
+
|
|
10
|
+
jest.mock(
|
|
11
|
+
"@applicaster/zapp-react-native-utils/appUtils/playerManager/player",
|
|
12
|
+
() => ({
|
|
13
|
+
Player: {
|
|
14
|
+
seekTo,
|
|
15
|
+
},
|
|
16
|
+
})
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
describe("PlayerLiveImageComponent", () => {
|
|
20
|
+
const defaultProps = {
|
|
21
|
+
item: { id: "1", title: "Test", type: { value: "" } },
|
|
22
|
+
style: {},
|
|
23
|
+
playerId: "player1",
|
|
24
|
+
imageKey: "image1",
|
|
25
|
+
audioMutedByDefault: false,
|
|
26
|
+
screenConfig: {},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const autoplay_settings = {
|
|
30
|
+
start_time: "00:10",
|
|
31
|
+
end_time: "00:35",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
beforeAll(() => {
|
|
35
|
+
jest.spyOn(PlayerFactory, "playerFactory").mockImplementation(() => ({
|
|
36
|
+
controller: new Player({}),
|
|
37
|
+
Component: () => null,
|
|
38
|
+
}));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should render correctly with default props", () => {
|
|
42
|
+
const component = render(<PlayerLiveImage {...defaultProps} />);
|
|
43
|
+
|
|
44
|
+
expect(component.toJSON()).toMatchSnapshot();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should call seekTo() on video loaded with autoplay settings", () => {
|
|
48
|
+
const props = {
|
|
49
|
+
...defaultProps,
|
|
50
|
+
item: {
|
|
51
|
+
...defaultProps.item,
|
|
52
|
+
extensions: { autoplay_settings },
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
render(<PlayerLiveImage {...props} />);
|
|
57
|
+
|
|
58
|
+
// TODO: implement this test
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should call pauseLiveImage() on video reached end timestamp", () => {
|
|
62
|
+
const props = {
|
|
63
|
+
...defaultProps,
|
|
64
|
+
item: {
|
|
65
|
+
...defaultProps.item,
|
|
66
|
+
extensions: { autoplay_settings },
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
render(<PlayerLiveImage {...props} />);
|
|
71
|
+
|
|
72
|
+
// TODO: implement this test
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`PlayerLiveImageComponent should render correctly with default props 1`] = `
|
|
4
|
+
<TrackedView
|
|
5
|
+
onPositionUpdated={[Function]}
|
|
6
|
+
testId="tracked-view-player1-Test"
|
|
7
|
+
>
|
|
8
|
+
<View
|
|
9
|
+
collapsable={false}
|
|
10
|
+
style={
|
|
11
|
+
Object {
|
|
12
|
+
"opacity": 1,
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
>
|
|
16
|
+
<View
|
|
17
|
+
accessibilityIgnoresInvertColors={true}
|
|
18
|
+
style={Object {}}
|
|
19
|
+
>
|
|
20
|
+
<Image
|
|
21
|
+
resizeMode="cover"
|
|
22
|
+
source={
|
|
23
|
+
Object {
|
|
24
|
+
"uri": undefined,
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
style={
|
|
28
|
+
Array [
|
|
29
|
+
Object {
|
|
30
|
+
"bottom": 0,
|
|
31
|
+
"left": 0,
|
|
32
|
+
"position": "absolute",
|
|
33
|
+
"right": 0,
|
|
34
|
+
"top": 0,
|
|
35
|
+
},
|
|
36
|
+
Object {
|
|
37
|
+
"height": undefined,
|
|
38
|
+
"width": undefined,
|
|
39
|
+
},
|
|
40
|
+
undefined,
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
/>
|
|
44
|
+
</View>
|
|
45
|
+
</View>
|
|
46
|
+
</TrackedView>
|
|
47
|
+
`;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { parseTime, getAutoplaySettings } from "../utils";
|
|
2
|
+
|
|
3
|
+
describe("parseTime", () => {
|
|
4
|
+
it("should return the parsed time in seconds", () => {
|
|
5
|
+
expect(parseTime("01:30")).toBe(90);
|
|
6
|
+
expect(parseTime("00:45")).toBe(45);
|
|
7
|
+
expect(parseTime("10:00")).toBe(600);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("should throw error for invalid time formats", () => {
|
|
11
|
+
const error = "Invalid time format";
|
|
12
|
+
expect(() => parseTime("")).toThrow(error);
|
|
13
|
+
expect(() => parseTime("abc")).toThrow(error);
|
|
14
|
+
expect(() => parseTime("12")).toThrow(error);
|
|
15
|
+
expect(() => parseTime(":12")).toThrow(error);
|
|
16
|
+
expect(() => parseTime("00:60")).toThrow(error);
|
|
17
|
+
expect(() => parseTime("-10:00")).toThrow(error);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe("getAutoplaySettings", () => {
|
|
22
|
+
it("should return the autoplay settings with parsed start and end times", () => {
|
|
23
|
+
const item = {
|
|
24
|
+
extensions: {
|
|
25
|
+
autoplay_settings: {
|
|
26
|
+
start_time: "01:30",
|
|
27
|
+
end_time: "02:00",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const { start, end } = getAutoplaySettings(item);
|
|
33
|
+
|
|
34
|
+
expect(start).toBe(90);
|
|
35
|
+
expect(end).toBe(120);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should return null for missing start or end times", () => {
|
|
39
|
+
const item = {
|
|
40
|
+
extensions: {
|
|
41
|
+
autoplay_settings: {
|
|
42
|
+
start_time: "01:30",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const { start, end } = getAutoplaySettings(item);
|
|
48
|
+
|
|
49
|
+
expect(start).toBe(90);
|
|
50
|
+
expect(end).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should return null for invalid start or end times", () => {
|
|
54
|
+
const item = {
|
|
55
|
+
extensions: {
|
|
56
|
+
autoplay_settings: {
|
|
57
|
+
start_time: "abc",
|
|
58
|
+
end_time: "def",
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const { start, end } = getAutoplaySettings(item);
|
|
64
|
+
|
|
65
|
+
expect(start).toBeNull();
|
|
66
|
+
expect(end).toBeNull();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should return null end time if start time greater than end time", () => {
|
|
70
|
+
const item = {
|
|
71
|
+
extensions: {
|
|
72
|
+
autoplay_settings: {
|
|
73
|
+
start_time: "02:00",
|
|
74
|
+
end_time: "01:30",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const { start, end } = getAutoplaySettings(item);
|
|
80
|
+
|
|
81
|
+
expect(start).toBe(120);
|
|
82
|
+
expect(end).toBeNull();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should return null end time if start time equal to end time", () => {
|
|
86
|
+
const item = {
|
|
87
|
+
extensions: {
|
|
88
|
+
autoplay_settings: {
|
|
89
|
+
start_time: "00:30",
|
|
90
|
+
end_time: "00:30",
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const { start, end } = getAutoplaySettings(item);
|
|
96
|
+
|
|
97
|
+
expect(start).toBe(30);
|
|
98
|
+
expect(end).toBeNull();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should return null for missing autoplay settings", () => {
|
|
102
|
+
const item = {};
|
|
103
|
+
|
|
104
|
+
const result = getAutoplaySettings(item);
|
|
105
|
+
|
|
106
|
+
expect(result.start).toBeNull();
|
|
107
|
+
expect(result.end).toBeNull();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { parseTimeToSeconds } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/OverlayObserver/utils";
|
|
2
|
+
import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
|
|
3
|
+
import { loggerLiveImageComponent } from "./loggerHelper";
|
|
4
|
+
|
|
5
|
+
const { log_error } = loggerLiveImageComponent;
|
|
6
|
+
|
|
7
|
+
type AutoplaySettings = {
|
|
8
|
+
start: number | null;
|
|
9
|
+
end: number | null;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const parseTime = (time: string): number => {
|
|
13
|
+
const seconds = toNumberWithDefaultZero(parseTimeToSeconds(time));
|
|
14
|
+
|
|
15
|
+
return Math.max(seconds, 0);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const getAutoplaySettings = (item: ZappEntry): AutoplaySettings => {
|
|
19
|
+
const autoplay_settings = item?.extensions?.autoplay_settings;
|
|
20
|
+
|
|
21
|
+
let start: number | null = null;
|
|
22
|
+
let end: number | null = null;
|
|
23
|
+
|
|
24
|
+
if (autoplay_settings) {
|
|
25
|
+
const { start_time, end_time } = autoplay_settings;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
start = start_time ? parseTime(start_time) : null;
|
|
29
|
+
end = end_time ? parseTime(end_time) : null;
|
|
30
|
+
|
|
31
|
+
if (start && end && start >= end) {
|
|
32
|
+
end = null;
|
|
33
|
+
}
|
|
34
|
+
} catch (error) {
|
|
35
|
+
log_error(`PlayerLiveImageComponent: ${error}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { start, end };
|
|
40
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { StyleProp,
|
|
1
|
+
import React, { useEffect, useRef } from "react";
|
|
2
|
+
import { StyleProp, ViewStyle, Animated, Easing } from "react-native";
|
|
3
3
|
import { useTargetScreenData } from "@applicaster/zapp-react-native-utils/reactHooks/screen";
|
|
4
4
|
import { ComponentsMap } from "@applicaster/zapp-react-native-ui-components/Components/River/ComponentsMap";
|
|
5
5
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
@@ -28,7 +28,7 @@ export const PlayerDetails = ({
|
|
|
28
28
|
style,
|
|
29
29
|
configuration,
|
|
30
30
|
isTabletLandscape,
|
|
31
|
-
isAudioPlayer
|
|
31
|
+
isAudioPlayer,
|
|
32
32
|
}: Props) => {
|
|
33
33
|
const screenData = useTargetScreenData(entry);
|
|
34
34
|
const insets = useSafeAreaInsets();
|
|
@@ -36,11 +36,47 @@ export const PlayerDetails = ({
|
|
|
36
36
|
const extraTabletStyles = !isAudioPlayer
|
|
37
37
|
? isTabletLandscape
|
|
38
38
|
? { marginTop: -insets.top, paddingTop: insets.top + 20 }
|
|
39
|
-
: { marginTop: -8, paddingTop: -8 }
|
|
39
|
+
: { marginTop: -8, paddingTop: -8 }
|
|
40
40
|
: {};
|
|
41
41
|
|
|
42
|
+
// Animation setup
|
|
43
|
+
const translateY = useRef(new Animated.Value(100)).current;
|
|
44
|
+
const opacity = useRef(new Animated.Value(0)).current;
|
|
45
|
+
|
|
46
|
+
const runAnimation = () => {
|
|
47
|
+
Animated.timing(translateY, {
|
|
48
|
+
toValue: 0,
|
|
49
|
+
duration: 500,
|
|
50
|
+
easing: Easing.inOut(Easing.ease),
|
|
51
|
+
useNativeDriver: true,
|
|
52
|
+
}).start();
|
|
53
|
+
|
|
54
|
+
Animated.timing(opacity, {
|
|
55
|
+
toValue: 1,
|
|
56
|
+
duration: 500,
|
|
57
|
+
easing: Easing.inOut(Easing.ease),
|
|
58
|
+
useNativeDriver: true,
|
|
59
|
+
}).start();
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (isAudioPlayer) {
|
|
64
|
+
setTimeout(() => runAnimation(), 1000);
|
|
65
|
+
}
|
|
66
|
+
}, [isAudioPlayer]);
|
|
67
|
+
|
|
42
68
|
return (
|
|
43
|
-
<View
|
|
69
|
+
<Animated.View
|
|
70
|
+
style={[
|
|
71
|
+
style,
|
|
72
|
+
extraTabletStyles,
|
|
73
|
+
containerStyle(configuration),
|
|
74
|
+
isAudioPlayer && {
|
|
75
|
+
transform: [{ translateY }],
|
|
76
|
+
opacity,
|
|
77
|
+
},
|
|
78
|
+
]}
|
|
79
|
+
>
|
|
44
80
|
{screenData && (
|
|
45
81
|
<ComponentsMap
|
|
46
82
|
riverId={screenData.id}
|
|
@@ -48,6 +84,6 @@ export const PlayerDetails = ({
|
|
|
48
84
|
riverComponents={screenData.ui_components}
|
|
49
85
|
/>
|
|
50
86
|
)}
|
|
51
|
-
</View>
|
|
87
|
+
</Animated.View>
|
|
52
88
|
);
|
|
53
89
|
};
|
|
@@ -8,6 +8,7 @@ import { PlayerDetails } from "./PlayerDetails";
|
|
|
8
8
|
import { playerDimesionsHack } from "./utils";
|
|
9
9
|
import { PathnameContext } from "../../Contexts/PathnameContext";
|
|
10
10
|
import { useDimensions } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
11
|
+
import { ScreenContextProvider } from "../../Contexts/ScreenContext";
|
|
11
12
|
|
|
12
13
|
type Dimensions = {
|
|
13
14
|
width: number;
|
|
@@ -181,35 +182,37 @@ export const PlayerWrapper = (props: Props) => {
|
|
|
181
182
|
playerDimesionsHack,
|
|
182
183
|
]}
|
|
183
184
|
>
|
|
184
|
-
<
|
|
185
|
-
<
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
185
|
+
<ScreenContextProvider pathname={"videoModal"}>
|
|
186
|
+
<PathnameContext.Provider value={"videoModal"}>
|
|
187
|
+
<View
|
|
188
|
+
testID={`${entry?.id}-player-container`}
|
|
189
|
+
style={[
|
|
190
|
+
defaultStyles.playerContainer,
|
|
191
|
+
containerDimensions,
|
|
192
|
+
containerStyle,
|
|
193
|
+
{
|
|
194
|
+
height:
|
|
195
|
+
isInlineModal && !docked
|
|
196
|
+
? containerDimensions.height + 10
|
|
197
|
+
: containerDimensions.height,
|
|
198
|
+
},
|
|
199
|
+
playerDimesionsHack,
|
|
200
|
+
]}
|
|
201
|
+
>
|
|
202
|
+
{children(childrenStyles)}
|
|
203
|
+
</View>
|
|
204
|
+
{isInlineModal && !docked ? (
|
|
205
|
+
<PlayerDetails
|
|
206
|
+
configuration={configuration}
|
|
207
|
+
style={defaultStyles.playerDetails}
|
|
208
|
+
entry={entry}
|
|
209
|
+
isTabletLandscape={isTabletLandscape}
|
|
210
|
+
/>
|
|
211
|
+
) : (
|
|
212
|
+
<View style={defaultStyles.playerDetails} />
|
|
213
|
+
)}
|
|
214
|
+
</PathnameContext.Provider>
|
|
215
|
+
</ScreenContextProvider>
|
|
213
216
|
</WrapperView>
|
|
214
217
|
);
|
|
215
218
|
};
|