@applicaster/zapp-react-native-ui-components 11.0.0-rc.99 → 12.0.0-alpha.5035843303

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/Components/BaseFocusable/index.ios.ts +1 -1
  2. package/Components/Cell/Cell.tsx +5 -13
  3. package/Components/Cell/{CellWithFocusable.js → CellWithFocusable.tsx} +32 -18
  4. package/Components/Cell/TvOSCellComponent.tsx +9 -12
  5. package/Components/FastImage/index.ios.js +3 -0
  6. package/Components/Focusable/FocusableTvOS.tsx +4 -1
  7. package/Components/FocusableCell/{index.js → index.tsx} +8 -5
  8. package/Components/FocusableGroup/FocusableTvOS.tsx +2 -2
  9. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +13 -8
  10. package/Components/HandlePlayable/HandlePlayable.tsx +2 -2
  11. package/Components/HookRenderer/HookRenderer.tsx +9 -6
  12. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +1 -1
  13. package/Components/Layout/TV/index.tsx +9 -13
  14. package/Components/Layout/index.tsx +9 -1
  15. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +3 -1
  16. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -1
  17. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +4 -0
  18. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +22 -19
  19. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +1 -0
  20. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/Image.test.tsx +4 -1
  21. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +2 -0
  22. package/Components/MasterCell/DefaultComponents/Toggle.tsx +5 -3
  23. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +4 -1
  24. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.tsx +10 -2
  25. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/CollapsibleTextContainer.tsx +9 -3
  26. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/__tests__/__snapshots__/index.test.tsx.snap +7 -1
  27. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/__tests__/index.test.tsx +12 -4
  28. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/index.ts +23 -9
  29. package/Components/MasterCell/hoc/withAsyncRender.tsx +8 -4
  30. package/Components/MasterCell/hooks/useAsyncRendering/MasterCellAsyncRenderManager.ts +12 -6
  31. package/Components/MasterCell/hooks/useAsyncRendering/__tests__/MasterCellAsyncRenderManager.test.ts +21 -0
  32. package/Components/MasterCell/hooks/useAsyncRendering/index.ts +15 -13
  33. package/Components/MasterCell/utils/__tests__/insertBetween.test.js +9 -0
  34. package/Components/MasterCell/utils/index.ts +143 -19
  35. package/Components/PlayerContainer/PlayerContainer.tsx +10 -6
  36. package/Components/River/ComponentsMap/ComponentsMap.tsx +4 -1
  37. package/Components/River/River.tsx +3 -4
  38. package/Components/River/TV/River.tsx +9 -4
  39. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +1 -0
  40. package/Components/River/index.tsx +3 -1
  41. package/Components/Screen/__tests__/Screen.test.tsx +6 -3
  42. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +4 -0
  43. package/Components/Screen/index.tsx +12 -44
  44. package/Components/Screen/navigationHandler.ts +1 -1
  45. package/Components/Spinner/SpinnerComponent.tsx +6 -2
  46. package/Components/VideoLive/LiveImageManager.ts +62 -3
  47. package/Components/VideoLive/PlayerLiveImageComponent.tsx +23 -1
  48. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +74 -0
  49. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +47 -0
  50. package/Components/VideoLive/__tests__/utils.test.ts +109 -0
  51. package/Components/VideoLive/utils.ts +40 -0
  52. package/Components/VideoModal/PlayerDetails.tsx +42 -6
  53. package/Components/VideoModal/PlayerWrapper.tsx +32 -29
  54. package/Components/VideoModal/VideoModal.tsx +20 -17
  55. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +21 -9
  56. package/Components/VideoModal/__tests__/__snapshots__/PlayerDetails.test.tsx.snap +14 -24
  57. package/Contexts/InteractionManagerContext/index.ts +0 -2
  58. package/Contexts/ScreenContext/index.tsx +118 -0
  59. package/package.json +5 -5
@@ -5,7 +5,6 @@ import { Text } from "react-native";
5
5
  import * as R from "ramda";
6
6
 
7
7
  import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
8
- import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
9
8
  import {
10
9
  useFeedLoader,
11
10
  useLayoutVersion,
@@ -17,6 +16,10 @@ import { utilsLogger } from "@applicaster/zapp-react-native-utils/logger";
17
16
  import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils";
18
17
  import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
19
18
  import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
19
+ import {
20
+ useNavbarState,
21
+ useSetNavbarState,
22
+ } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
20
23
 
21
24
  type Props = {
22
25
  screenId: string;
@@ -43,8 +46,10 @@ export const River = (props: Props) => {
43
46
  extraOffset,
44
47
  } = props;
45
48
 
46
- const { setScreenTitle, setScreenSummary, screenTitle, screenSummary } =
47
- useNavigation();
49
+ const { title: screenTitle, summary: screenSummary } = useNavbarState();
50
+
51
+ const { setTitle: setScreenTitle, setSummary: setScreenSummary } =
52
+ useSetNavbarState();
48
53
 
49
54
  const navigationPluginData = useNavigationPluginData();
50
55
 
@@ -92,7 +97,7 @@ export const River = (props: Props) => {
92
97
  setScreenTitle(stringOrEmpty(screenData?.title));
93
98
  setScreenSummary(stringOrEmpty(screenData?.summary));
94
99
  }
95
- }, [feedData, screenData]);
100
+ }, [feedData, screenData, screenTitle, screenSummary]);
96
101
 
97
102
  React.useEffect(() => {
98
103
  if (
@@ -60,6 +60,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
60
60
  onScroll={[Function]}
61
61
  onScrollBeginDrag={[Function]}
62
62
  onScrollEndDrag={[Function]}
63
+ overScrollMode="auto"
63
64
  refreshControl={null}
64
65
  removeClippedSubviews={true}
65
66
  renderItem={[Function]}
@@ -7,6 +7,7 @@ import { ContainerContext } from "../../Contexts";
7
7
  import { RiverComponent } from "./River";
8
8
  import { withDimensionsProvider } from "@applicaster/zapp-react-native-utils/dimensionsProvider";
9
9
  import { WithRiverFeedLoader } from "../../Decorators/RiverFeedLoader";
10
+ import { withScreenContext } from "../../Contexts/ScreenContext";
10
11
 
11
12
  const reduxConnector = connectToStore(R.pick(["rivers", "appData"]));
12
13
 
@@ -16,5 +17,6 @@ export const River = R.compose(
16
17
  WithRiverFeedLoader,
17
18
  withNavigator,
18
19
  withDimensionsProvider,
19
- ContainerContext.withConsumer
20
+ ContainerContext.withConsumer,
21
+ withScreenContext
20
22
  )(RiverComponent);
@@ -53,6 +53,9 @@ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/screen", () => ({
53
53
  useCurrentScreenData: jest.fn(() => ({
54
54
  id: "testId",
55
55
  })),
56
+ useNavbarState: jest.fn(() => ({
57
+ visible: jest.fn(() => mock_navBarVisibleFlag),
58
+ })),
56
59
  }));
57
60
 
58
61
  const {
@@ -67,11 +70,11 @@ const navBarProps = { nav_items: [] };
67
70
  const screenData = {};
68
71
 
69
72
  jest.mock("@applicaster/zapp-react-native-utils/reactHooks/navigation", () => ({
70
- useNavBarStateForScreen: jest.fn(() => ({
71
- isNavBarVisible: jest.fn(() => mock_navBarVisibleFlag),
72
- canGoBack: () => false,
73
+ useRoute: jest.fn(() => ({
74
+ pathname: "/river/testId",
73
75
  })),
74
76
  useNavigation: jest.fn(() => ({
77
+ canGoBack: () => false,
75
78
  currentRoute: "/river/testId",
76
79
  data: { screen: { id: "testId" } },
77
80
  })),
@@ -9,6 +9,10 @@ exports[`<Screen Component /> when the navbar should be hidden renders correctly
9
9
  }
10
10
  }
11
11
  >
12
+ <View
13
+ nav_items={Array []}
14
+ testID="navBar"
15
+ />
12
16
  <View>
13
17
  <View
14
18
  pathname="/foo/path"
@@ -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 { Platform, StatusBar, View } from "react-native";
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
- <View style={style}>
123
- {navBarVisible && <NavBar key={pathname} {...navBarProps} />}
124
- {shouldHideStatusBar && <StatusBar hidden={Platform.OS === "android"} />}
125
-
126
- <OfflineFallbackScreen screenData={screenData}>
127
- <RouteManager pathname={pathname} screenData={screenData} />
128
- </OfflineFallbackScreen>
129
- </View>
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
  }
@@ -19,7 +19,7 @@ export function isNavBarVisible(
19
19
  screenData,
20
20
  showNavBar,
21
21
  canGoBack,
22
- videoModalState = null
22
+ videoModalState: QuickBrickVideoModalState | null = null
23
23
  ) {
24
24
  /* screenData is not actual navigator.data
25
25
  * or navigator.screenData,
@@ -8,11 +8,15 @@ type ActivityIndicatorThemeValues = {
8
8
  loading_spinner_color: ActivityIndicatorProps["color"];
9
9
  };
10
10
 
11
- export function SpinnerComponent(props: ActivityIndicatorProps) {
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
- if (isAndroidTVPlatform()) {
17
+ const { showAnyway = false } = props;
18
+
19
+ if (isAndroidTVPlatform() && !showAnyway) {
16
20
  return null;
17
21
  }
18
22
 
@@ -9,7 +9,12 @@ import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
9
9
 
10
10
  const { log_debug, log_info, log_error } = loggerLiveImageManager;
11
11
 
12
- export type LiveImageManagerEvent = "onEnd" | "onStart" | "onError";
12
+ export type LiveImageManagerEvent =
13
+ | "onEnd"
14
+ | "onStart"
15
+ | "onError"
16
+ | "onDestroy"
17
+ | "onPlayerClose";
13
18
 
14
19
  export type LiveImageManagerListenerData = {
15
20
  id: string;
@@ -100,6 +105,15 @@ export class LiveImageManager implements PlayerLifecycleListener {
100
105
  }
101
106
 
102
107
  this.items = this.items.filter((i) => i !== item);
108
+
109
+ this.invokeListenersUpdate({
110
+ callbackName: "onDestroy",
111
+ event: {
112
+ item,
113
+ playerId: this.currentlyPlaying?.playerId,
114
+ primaryPlayerId: this.primaryPlayer?.playerId,
115
+ },
116
+ });
103
117
  };
104
118
 
105
119
  public onViewportEnter = (item: LiveImage) => {
@@ -351,9 +365,11 @@ export class LiveImageManager implements PlayerLifecycleListener {
351
365
  }, primaryPlayerId: ${this.primaryPlayer?.playerId}`
352
366
  );
353
367
 
368
+ const isCurrentItemEnded = this.currentlyPlaying === item;
369
+
354
370
  // TODO: This branch was used when we had play button to toggle replay.
355
371
  // Now we mute button instead but we keep it just in case.
356
- if (IS_ALLOWED_REPLAY && !isTV() && this.currentlyPlaying === item) {
372
+ if (IS_ALLOWED_REPLAY && !isTV() && isCurrentItemEnded) {
357
373
  this.currentlyPlaying.player.seekTo(0);
358
374
  this.currentlyPlaying.player.pause();
359
375
 
@@ -362,8 +378,29 @@ export class LiveImageManager implements PlayerLifecycleListener {
362
378
  item.setMode(LiveImageType.Image);
363
379
  }
364
380
 
381
+ // Prevent receiving onEnd event from both `close` and `end` player events
382
+ isCurrentItemEnded &&
383
+ this.invokeListenersUpdate({
384
+ callbackName: "onEnd",
385
+ event: {
386
+ item,
387
+ playerId: this.currentlyPlaying?.playerId,
388
+ primaryPlayerId: this.primaryPlayer?.playerId,
389
+ },
390
+ });
391
+ };
392
+
393
+ onLiveImagePlayerClose = (item: LiveImage) => {
394
+ log_debug(
395
+ `onLiveImagePlayerClose: live-image ${playerInfo(
396
+ item.player
397
+ )}, currentPlayingId: ${
398
+ this.currentlyPlaying?.playerId
399
+ }, primaryPlayerId: ${this.primaryPlayer?.playerId}`
400
+ );
401
+
365
402
  this.invokeListenersUpdate({
366
- callbackName: "onEnd",
403
+ callbackName: "onPlayerClose",
367
404
  event: {
368
405
  item,
369
406
  playerId: this.currentlyPlaying?.playerId,
@@ -475,6 +512,16 @@ export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
475
512
  LiveImageManager.instance.onLiveImageError(this, error);
476
513
  };
477
514
 
515
+ onVideoError = (error: Error) => {
516
+ log_error(
517
+ `onVideoError: live-image, fail to play, switching back to image. error: ${
518
+ error?.message
519
+ }, ${playerInfo(this.player)}`
520
+ );
521
+
522
+ LiveImageManager.instance.onLiveImageError(this, error);
523
+ };
524
+
478
525
  onVideoEnd = (_event) => {
479
526
  log_info(
480
527
  `onVideoEnd: live-image, video completed, switching back to image, ${playerInfo(
@@ -484,4 +531,16 @@ export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
484
531
 
485
532
  LiveImageManager.instance.onLiveImageEnded(this);
486
533
  };
534
+
535
+ onPlayerClose = () => {
536
+ log_info(
537
+ `onPlayerClose: live-image, player closed, switching back to image, ${playerInfo(
538
+ this.player
539
+ )}`
540
+ );
541
+
542
+ // TODO: we maybe want to add onPlayerClose to separate completion behavior
543
+ LiveImageManager.instance.onLiveImagePlayerClose(this);
544
+ LiveImageManager.instance.onLiveImageEnded(this);
545
+ };
487
546
  }
@@ -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
+ };