@applicaster/zapp-react-native-ui-components 14.0.0-alpha.1308901965 → 14.0.0-alpha.1322105203
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/Focusable/Focusable.tsx +5 -3
- package/Components/Focusable/FocusableiOS.tsx +1 -1
- package/Components/HandlePlayable/HandlePlayable.tsx +11 -1
- package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -27
- package/Components/PlayerContainer/PlayerContainer.tsx +15 -6
- package/Components/ScreenRevealManager/ScreenRevealManager.ts +76 -0
- package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +107 -0
- package/Components/ScreenRevealManager/__tests__/withScreenRevealManager.test.tsx +96 -0
- package/Components/ScreenRevealManager/index.ts +1 -0
- package/Components/ScreenRevealManager/withScreenRevealManager.tsx +79 -0
- package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +9 -1
- package/Components/VideoModal/PlayerDetails.tsx +24 -2
- package/Components/VideoModal/PlayerWrapper.tsx +26 -142
- package/Components/VideoModal/VideoModal.tsx +3 -17
- package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -7
- package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +44 -240
- package/Components/VideoModal/hooks/index.ts +0 -2
- package/Components/VideoModal/hooks/useModalSize.ts +18 -2
- package/Components/VideoModal/utils.ts +6 -0
- package/Contexts/ScreenDataContext/index.tsx +0 -2
- package/package.json +5 -8
- package/Components/VideoModal/hooks/useBackgroundColor.ts +0 -10
|
@@ -5,6 +5,7 @@ import { BaseFocusable } from "../BaseFocusable";
|
|
|
5
5
|
import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
|
|
6
6
|
import { LONG_KEY_PRESS_TIMEOUT } from "@applicaster/quick-brick-core/const";
|
|
7
7
|
import { withFocusableContext } from "../../Contexts/FocusableGroupContext/withFocusableContext";
|
|
8
|
+
import { StyleSheet, ViewStyle } from "react-native";
|
|
8
9
|
|
|
9
10
|
type Props = {
|
|
10
11
|
initialFocus?: boolean;
|
|
@@ -21,7 +22,7 @@ type Props = {
|
|
|
21
22
|
handleFocus?: ({ mouse }: { mouse: boolean }) => void;
|
|
22
23
|
children: (boolean, string) => React.ComponentType<any>;
|
|
23
24
|
selected?: boolean;
|
|
24
|
-
style?:
|
|
25
|
+
style?: ViewStyle[] | ViewStyle;
|
|
25
26
|
};
|
|
26
27
|
|
|
27
28
|
class Focusable extends BaseFocusable<Props> {
|
|
@@ -122,7 +123,7 @@ class Focusable extends BaseFocusable<Props> {
|
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
render() {
|
|
125
|
-
const { children, style } = this.props;
|
|
126
|
+
const { children, style, ...otherProps } = this.props;
|
|
126
127
|
const { focused } = this.state;
|
|
127
128
|
|
|
128
129
|
const id = this.getId();
|
|
@@ -139,7 +140,8 @@ class Focusable extends BaseFocusable<Props> {
|
|
|
139
140
|
onMouseUp={this.pressOut}
|
|
140
141
|
data-testid={focusableId}
|
|
141
142
|
focused-teststate={focused ? "focused" : "default"}
|
|
142
|
-
style={style}
|
|
143
|
+
style={StyleSheet.flatten(style) as any as React.CSSProperties}
|
|
144
|
+
{...otherProps}
|
|
143
145
|
>
|
|
144
146
|
{children(focused, { mouse: this.mouse })}
|
|
145
147
|
</div>
|
|
@@ -7,12 +7,14 @@ import {
|
|
|
7
7
|
import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
|
|
8
8
|
import {
|
|
9
9
|
useDimensions,
|
|
10
|
+
useIsTablet as isTablet,
|
|
10
11
|
useNavigation,
|
|
11
12
|
} from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
12
13
|
|
|
13
14
|
import { BufferAnimation } from "../PlayerContainer/BufferAnimation";
|
|
14
15
|
import { PlayerContainer } from "../PlayerContainer";
|
|
15
16
|
import { useModalSize } from "../VideoModal/hooks";
|
|
17
|
+
import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
16
18
|
|
|
17
19
|
type Props = {
|
|
18
20
|
item: ZappEntry;
|
|
@@ -83,6 +85,13 @@ type PlayableComponent = {
|
|
|
83
85
|
Component: React.ComponentType<any>;
|
|
84
86
|
};
|
|
85
87
|
|
|
88
|
+
const dimensionsContext: "window" | "screen" = platformSelect({
|
|
89
|
+
android_tv: "window",
|
|
90
|
+
amazon: "window",
|
|
91
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
92
|
+
default: isTablet() ? "window" : "screen", // on tablet, window represents correct values, on phone it's not as the screen could be rotated
|
|
93
|
+
});
|
|
94
|
+
|
|
86
95
|
export function HandlePlayable({
|
|
87
96
|
item,
|
|
88
97
|
isModal,
|
|
@@ -135,7 +144,8 @@ export function HandlePlayable({
|
|
|
135
144
|
});
|
|
136
145
|
}, [casting]);
|
|
137
146
|
|
|
138
|
-
const { width: screenWidth, height: screenHeight } =
|
|
147
|
+
const { width: screenWidth, height: screenHeight } =
|
|
148
|
+
useDimensions(dimensionsContext);
|
|
139
149
|
|
|
140
150
|
const modalSize = useModalSize();
|
|
141
151
|
|
|
@@ -2,7 +2,6 @@ import React, { useMemo } from "react";
|
|
|
2
2
|
import { ImageStyle } from "react-native";
|
|
3
3
|
import { Focusable } from "@applicaster/zapp-react-native-ui-components/Components/Focusable";
|
|
4
4
|
import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
|
|
5
|
-
import * as R from "ramda";
|
|
6
5
|
import { getXray } from "@applicaster/zapp-react-native-utils/logger";
|
|
7
6
|
import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
|
|
8
7
|
import { useAccessibilityManager } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager/hooks";
|
|
@@ -67,32 +66,10 @@ export function FocusableView({ style, children, item, ...otherProps }: Props) {
|
|
|
67
66
|
const handleFocus = (focusable) => {
|
|
68
67
|
const focusedButtonId = getFocusedButtonId(focusable);
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const right = left + width;
|
|
75
|
-
|
|
76
|
-
const boundingRect = {
|
|
77
|
-
x,
|
|
78
|
-
y,
|
|
79
|
-
pageX,
|
|
80
|
-
pageY,
|
|
81
|
-
width,
|
|
82
|
-
height,
|
|
83
|
-
top,
|
|
84
|
-
bottom,
|
|
85
|
-
left,
|
|
86
|
-
right,
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
otherProps?.onToggleFocus?.({
|
|
90
|
-
focusable: {
|
|
91
|
-
getRect: R.always(boundingRect),
|
|
92
|
-
},
|
|
93
|
-
focusedButtonId,
|
|
94
|
-
mouse: focusable.mouse,
|
|
95
|
-
});
|
|
69
|
+
otherProps?.onToggleFocus?.({
|
|
70
|
+
focusable: wrapperRef.current,
|
|
71
|
+
focusedButtonId,
|
|
72
|
+
mouse: focusable.mouse,
|
|
96
73
|
});
|
|
97
74
|
|
|
98
75
|
if (ttsLabel) {
|
|
@@ -88,7 +88,7 @@ export const VideoModalMode = {
|
|
|
88
88
|
MAXIMIZED: "MAXIMIZED",
|
|
89
89
|
MINIMIZED: "MINIMIZED",
|
|
90
90
|
FULLSCREEN: "FULLSCREEN",
|
|
91
|
-
};
|
|
91
|
+
} as const;
|
|
92
92
|
|
|
93
93
|
export type PlayNextData = {
|
|
94
94
|
state: PlayNextState;
|
|
@@ -127,7 +127,7 @@ const webStyles = {
|
|
|
127
127
|
playerScreen: {
|
|
128
128
|
flex: 1,
|
|
129
129
|
height: "100vh",
|
|
130
|
-
|
|
130
|
+
backgroundColor: "black",
|
|
131
131
|
},
|
|
132
132
|
playerWrapper: {
|
|
133
133
|
height: "100%",
|
|
@@ -145,7 +145,6 @@ const nativeStyles = {
|
|
|
145
145
|
},
|
|
146
146
|
playerScreen: {
|
|
147
147
|
flex: 1,
|
|
148
|
-
backgroundColor: "black",
|
|
149
148
|
overflow: "hidden",
|
|
150
149
|
},
|
|
151
150
|
playerWrapper: {
|
|
@@ -565,8 +564,9 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
565
564
|
const isInlineTV = isInlineTVUtil(screenData);
|
|
566
565
|
|
|
567
566
|
const inline =
|
|
568
|
-
[VideoModalMode.MAXIMIZED, VideoModalMode.MINIMIZED].includes(
|
|
569
|
-
|
|
567
|
+
[VideoModalMode.MAXIMIZED, VideoModalMode.MINIMIZED].includes(
|
|
568
|
+
mode as any
|
|
569
|
+
) || isInlineTV;
|
|
570
570
|
|
|
571
571
|
const value = React.useMemo(
|
|
572
572
|
() => ({ playerId: state.playerId }),
|
|
@@ -587,7 +587,11 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
587
587
|
);
|
|
588
588
|
}
|
|
589
589
|
|
|
590
|
-
if (
|
|
590
|
+
if (
|
|
591
|
+
screen_background_color &&
|
|
592
|
+
mode !== VideoModalMode.FULLSCREEN &&
|
|
593
|
+
isTV()
|
|
594
|
+
) {
|
|
591
595
|
updatedStyles.playerScreen.backgroundColor = screen_background_color;
|
|
592
596
|
}
|
|
593
597
|
|
|
@@ -617,6 +621,8 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
617
621
|
playNextData,
|
|
618
622
|
};
|
|
619
623
|
|
|
624
|
+
const pointerEventsProp = mode === "MINIMIZED" ? "box-none" : "auto";
|
|
625
|
+
|
|
620
626
|
return (
|
|
621
627
|
<PlayerStateContext.Provider value={value}>
|
|
622
628
|
<PlayerContainerContextProvider
|
|
@@ -637,14 +643,17 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
637
643
|
preferredFocus
|
|
638
644
|
shouldUsePreferredFocus
|
|
639
645
|
groupId={groupId}
|
|
646
|
+
pointerEvents={pointerEventsProp}
|
|
640
647
|
>
|
|
641
648
|
{/* Video player and components */}
|
|
642
649
|
<View
|
|
643
650
|
style={styles.playerScreen}
|
|
644
651
|
testID={"player-screen-container"}
|
|
652
|
+
pointerEvents={pointerEventsProp}
|
|
645
653
|
>
|
|
646
654
|
{/* Player container */}
|
|
647
655
|
<View
|
|
656
|
+
pointerEvents={pointerEventsProp}
|
|
648
657
|
style={[
|
|
649
658
|
styles.playerWrapper,
|
|
650
659
|
// eslint-disable-next-line react-native/no-inline-styles, react-native/no-color-literals
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { makeListOf } from "@applicaster/zapp-react-native-utils/arrayUtils";
|
|
2
|
+
import { isFirstComponentGallery } from "@applicaster/zapp-react-native-utils/componentsUtils";
|
|
3
|
+
import { once } from "ramda";
|
|
4
|
+
|
|
5
|
+
const INITIAL_NUMBER_TO_LOAD = 3;
|
|
6
|
+
|
|
7
|
+
// Infer the values of COMPONENT_LOADING_STATE as a type
|
|
8
|
+
type ComponentLoadingState =
|
|
9
|
+
(typeof COMPONENT_LOADING_STATE)[keyof typeof COMPONENT_LOADING_STATE];
|
|
10
|
+
|
|
11
|
+
export const COMPONENT_LOADING_STATE = {
|
|
12
|
+
UNKNOWN: "UNKNOWN",
|
|
13
|
+
LOADED_WITH_SUCCESS: "LOADED_WITH_SUCCESS",
|
|
14
|
+
LOADED_WITH_FAILURE: "LOADED_WITH_FAILURE",
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
// Function to get the number of loaded components
|
|
18
|
+
const getNumberOfLoaded = (states: ComponentLoadingState[]): number => {
|
|
19
|
+
return states.filter((value) => value !== COMPONENT_LOADING_STATE.UNKNOWN)
|
|
20
|
+
.length;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const getNumberOfComponentsWaitToLoadBeforePresent = (
|
|
24
|
+
componentsToRender: ZappUIComponent[]
|
|
25
|
+
): number => {
|
|
26
|
+
// when Gallery is the first component, no need to wait the others
|
|
27
|
+
if (isFirstComponentGallery(componentsToRender)) {
|
|
28
|
+
return 1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return Math.min(INITIAL_NUMBER_TO_LOAD, componentsToRender.length);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export class ScreenRevealManager {
|
|
35
|
+
public numberOfComponentsWaitToLoadBeforePresent: number;
|
|
36
|
+
private renderingState: Array<ComponentLoadingState>;
|
|
37
|
+
private callback: Callback;
|
|
38
|
+
|
|
39
|
+
constructor(componentsToRender: ZappUIComponent[], callback: Callback) {
|
|
40
|
+
this.numberOfComponentsWaitToLoadBeforePresent =
|
|
41
|
+
getNumberOfComponentsWaitToLoadBeforePresent(componentsToRender);
|
|
42
|
+
|
|
43
|
+
this.renderingState = makeListOf<ComponentLoadingState>(
|
|
44
|
+
COMPONENT_LOADING_STATE.UNKNOWN,
|
|
45
|
+
this.numberOfComponentsWaitToLoadBeforePresent
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
this.callback = once(callback);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
onLoadFinished = (index: number): void => {
|
|
52
|
+
this.renderingState[index] = COMPONENT_LOADING_STATE.LOADED_WITH_SUCCESS;
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
getNumberOfLoaded(this.renderingState) >=
|
|
56
|
+
this.numberOfComponentsWaitToLoadBeforePresent
|
|
57
|
+
) {
|
|
58
|
+
this.setIsReadyToShow();
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
onLoadFailed = (index: number): void => {
|
|
63
|
+
this.renderingState[index] = COMPONENT_LOADING_STATE.LOADED_WITH_FAILURE;
|
|
64
|
+
|
|
65
|
+
if (
|
|
66
|
+
getNumberOfLoaded(this.renderingState) >=
|
|
67
|
+
this.numberOfComponentsWaitToLoadBeforePresent
|
|
68
|
+
) {
|
|
69
|
+
this.setIsReadyToShow();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
setIsReadyToShow = (): void => {
|
|
74
|
+
this.callback();
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ScreenRevealManager,
|
|
3
|
+
COMPONENT_LOADING_STATE,
|
|
4
|
+
} from "../ScreenRevealManager";
|
|
5
|
+
|
|
6
|
+
describe("ScreenRevealManager", () => {
|
|
7
|
+
const mockCallback = jest.fn();
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
jest.clearAllMocks();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("should initialize with the correct number of components to wait for", () => {
|
|
14
|
+
const componentsToRender: ZappUIComponent[] = [
|
|
15
|
+
{ component_type: "component1" },
|
|
16
|
+
{ component_type: "component2" },
|
|
17
|
+
{ component_type: "component3" },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const manager = new ScreenRevealManager(componentsToRender, mockCallback);
|
|
21
|
+
|
|
22
|
+
expect(manager["numberOfComponentsWaitToLoadBeforePresent"]).toBe(3);
|
|
23
|
+
|
|
24
|
+
expect(manager["renderingState"]).toEqual([
|
|
25
|
+
COMPONENT_LOADING_STATE.UNKNOWN,
|
|
26
|
+
COMPONENT_LOADING_STATE.UNKNOWN,
|
|
27
|
+
COMPONENT_LOADING_STATE.UNKNOWN,
|
|
28
|
+
]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should call the callback when the required number of components are loaded successfully", () => {
|
|
32
|
+
const componentsToRender: ZappUIComponent[] = [
|
|
33
|
+
{ component_type: "component1" },
|
|
34
|
+
{ component_type: "component2" },
|
|
35
|
+
{ component_type: "component3" },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const manager = new ScreenRevealManager(componentsToRender, mockCallback);
|
|
39
|
+
|
|
40
|
+
manager.onLoadFinished(0);
|
|
41
|
+
manager.onLoadFinished(1);
|
|
42
|
+
manager.onLoadFinished(2);
|
|
43
|
+
|
|
44
|
+
expect(mockCallback).toHaveBeenCalledTimes(1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should call the callback when the required number of components fail to load", () => {
|
|
48
|
+
const componentsToRender: ZappUIComponent[] = [
|
|
49
|
+
{ component_type: "component1" },
|
|
50
|
+
{ component_type: "component2" },
|
|
51
|
+
{ component_type: "component3" },
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const manager = new ScreenRevealManager(componentsToRender, mockCallback);
|
|
55
|
+
|
|
56
|
+
manager.onLoadFailed(0);
|
|
57
|
+
manager.onLoadFailed(1);
|
|
58
|
+
manager.onLoadFailed(2);
|
|
59
|
+
|
|
60
|
+
expect(mockCallback).toHaveBeenCalledTimes(1);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should call the callback when a mix of successful and failed loads meet the required number", () => {
|
|
64
|
+
const componentsToRender: ZappUIComponent[] = [
|
|
65
|
+
{ component_type: "component1" },
|
|
66
|
+
{ component_type: "component2" },
|
|
67
|
+
{ component_type: "component3" },
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const manager = new ScreenRevealManager(componentsToRender, mockCallback);
|
|
71
|
+
|
|
72
|
+
manager.onLoadFinished(0);
|
|
73
|
+
manager.onLoadFailed(1);
|
|
74
|
+
manager.onLoadFinished(2);
|
|
75
|
+
|
|
76
|
+
expect(mockCallback).toHaveBeenCalledTimes(1);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should not call the callback if the required number of components are not loaded", () => {
|
|
80
|
+
const componentsToRender: ZappUIComponent[] = [
|
|
81
|
+
{ component_type: "component1" },
|
|
82
|
+
{ component_type: "component2" },
|
|
83
|
+
{ component_type: "component3" },
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
const manager = new ScreenRevealManager(componentsToRender, mockCallback);
|
|
87
|
+
|
|
88
|
+
manager.onLoadFinished(0);
|
|
89
|
+
manager.onLoadFailed(1);
|
|
90
|
+
|
|
91
|
+
expect(mockCallback).not.toHaveBeenCalled();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should call the callback when the when first component is gallery and it was loaded successfully", () => {
|
|
95
|
+
const componentsToRender: ZappUIComponent[] = [
|
|
96
|
+
{ component_type: "gallery-qb" },
|
|
97
|
+
{ component_type: "component2" },
|
|
98
|
+
{ component_type: "component3" },
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
const manager = new ScreenRevealManager(componentsToRender, mockCallback);
|
|
102
|
+
|
|
103
|
+
manager.onLoadFinished(0);
|
|
104
|
+
|
|
105
|
+
expect(mockCallback).toHaveBeenCalledTimes(1);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/* eslint-disable react/prop-types */
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { render, screen, act } from "@testing-library/react-native";
|
|
5
|
+
import { View } from "react-native";
|
|
6
|
+
import {
|
|
7
|
+
withScreenRevealManager,
|
|
8
|
+
SHOWN,
|
|
9
|
+
TIMEOUT,
|
|
10
|
+
} from "../withScreenRevealManager";
|
|
11
|
+
|
|
12
|
+
jest.mock("react-native/Libraries/Animated/NativeAnimatedHelper");
|
|
13
|
+
|
|
14
|
+
const MockComponent = ({
|
|
15
|
+
initialNumberToLoad,
|
|
16
|
+
onLoadFinishedFromScreenRevealManager,
|
|
17
|
+
onLoadFailedFromScreenRevealManager,
|
|
18
|
+
}) => {
|
|
19
|
+
React.useEffect(() => {
|
|
20
|
+
// Simulate loading components
|
|
21
|
+
for (let i = 0; i < initialNumberToLoad; i++) {
|
|
22
|
+
onLoadFinishedFromScreenRevealManager(i);
|
|
23
|
+
}
|
|
24
|
+
}, [initialNumberToLoad, onLoadFinishedFromScreenRevealManager]);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<View
|
|
28
|
+
testID="mock-component"
|
|
29
|
+
initialNumberToLoad={initialNumberToLoad}
|
|
30
|
+
onLoadFinishedFromScreenRevealManager={
|
|
31
|
+
onLoadFinishedFromScreenRevealManager
|
|
32
|
+
}
|
|
33
|
+
onLoadFailedFromScreenRevealManager={onLoadFailedFromScreenRevealManager}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const WrappedComponent = withScreenRevealManager(MockComponent);
|
|
39
|
+
|
|
40
|
+
describe("withScreenRevealManager", () => {
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
jest.clearAllMocks();
|
|
43
|
+
jest.useFakeTimers();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
jest.runOnlyPendingTimers();
|
|
48
|
+
jest.useRealTimers();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should render the wrapped component", () => {
|
|
52
|
+
render(
|
|
53
|
+
<WrappedComponent
|
|
54
|
+
componentsToRender={[{ id: "1" }, { id: "2" }, { id: "3" }]}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
expect(screen.getByTestId("mock-component")).toBeTruthy();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should animate opacity when ready to show", () => {
|
|
62
|
+
render(
|
|
63
|
+
<WrappedComponent
|
|
64
|
+
componentsToRender={[{ id: "1" }, { id: "2" }, { id: "3" }]}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const animatedView = screen.getByTestId("animated-component");
|
|
69
|
+
|
|
70
|
+
act(() => {
|
|
71
|
+
jest.advanceTimersByTime(TIMEOUT + 100);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(animatedView.props.style.opacity).toBe(SHOWN);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should pass initialNumberToLoad, onLoadFinishedFromScreenRevealManager, and onLoadFailedFromScreenRevealManager to the wrapped component", () => {
|
|
78
|
+
render(
|
|
79
|
+
<WrappedComponent
|
|
80
|
+
componentsToRender={[{ id: "1" }, { id: "2" }, { id: "3" }]}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const mockComponent = screen.getByTestId("mock-component");
|
|
85
|
+
|
|
86
|
+
expect(mockComponent.props.initialNumberToLoad).toBe(3);
|
|
87
|
+
|
|
88
|
+
expect(
|
|
89
|
+
mockComponent.props.onLoadFinishedFromScreenRevealManager
|
|
90
|
+
).toBeInstanceOf(Function);
|
|
91
|
+
|
|
92
|
+
expect(
|
|
93
|
+
mockComponent.props.onLoadFailedFromScreenRevealManager
|
|
94
|
+
).toBeInstanceOf(Function);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { withScreenRevealManager } from "./withScreenRevealManager";
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Animated } from "react-native";
|
|
3
|
+
import { isFirstComponentScreenPicker } from "@applicaster/zapp-react-native-utils/componentsUtils";
|
|
4
|
+
import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
5
|
+
import { useRefWithInitialValue } from "@applicaster/zapp-react-native-utils/reactHooks/state/useRefWithInitialValue";
|
|
6
|
+
|
|
7
|
+
import { ScreenRevealManager } from "./ScreenRevealManager";
|
|
8
|
+
|
|
9
|
+
const flex = platformSelect({
|
|
10
|
+
tvos: 1,
|
|
11
|
+
android_tv: 1,
|
|
12
|
+
web: undefined,
|
|
13
|
+
samsung_tv: undefined,
|
|
14
|
+
lg_tv: undefined,
|
|
15
|
+
default: undefined,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const TIMEOUT = 500; // 500 ms
|
|
19
|
+
|
|
20
|
+
const HIDDEN = 0; // opacity = 0
|
|
21
|
+
|
|
22
|
+
export const SHOWN = 1; // opacity = 1
|
|
23
|
+
|
|
24
|
+
type Props = {
|
|
25
|
+
componentsToRender: ZappUIComponent[];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const withScreenRevealManager = (Component) => {
|
|
29
|
+
return function WithScreenRevealManager(props: Props) {
|
|
30
|
+
const { componentsToRender } = props;
|
|
31
|
+
|
|
32
|
+
const [isReadyToShow, setIsReadyToShow] = React.useState(false);
|
|
33
|
+
|
|
34
|
+
const handleSetIsReadyToShow = React.useCallback(() => {
|
|
35
|
+
setIsReadyToShow(true);
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
const managerRef = useRefWithInitialValue<ScreenRevealManager>(
|
|
39
|
+
() => new ScreenRevealManager(componentsToRender, handleSetIsReadyToShow)
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const opacityRef = useRefWithInitialValue<Animated.Value>(
|
|
43
|
+
() => new Animated.Value(HIDDEN)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
React.useEffect(() => {
|
|
47
|
+
if (isReadyToShow) {
|
|
48
|
+
Animated.timing(opacityRef.current, {
|
|
49
|
+
toValue: SHOWN,
|
|
50
|
+
duration: TIMEOUT,
|
|
51
|
+
useNativeDriver: true,
|
|
52
|
+
}).start();
|
|
53
|
+
}
|
|
54
|
+
}, [isReadyToShow]);
|
|
55
|
+
|
|
56
|
+
if (isFirstComponentScreenPicker(componentsToRender)) {
|
|
57
|
+
// for screen-picker with have additional internal ComponentsMap, no need to add this wrapper
|
|
58
|
+
return <Component {...props} />;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Animated.View
|
|
63
|
+
style={{ opacity: opacityRef.current, flex }}
|
|
64
|
+
testID="animated-component"
|
|
65
|
+
>
|
|
66
|
+
<Component
|
|
67
|
+
{...props}
|
|
68
|
+
initialNumberToLoad={
|
|
69
|
+
managerRef.current.numberOfComponentsWaitToLoadBeforePresent
|
|
70
|
+
}
|
|
71
|
+
onLoadFinishedFromScreenRevealManager={
|
|
72
|
+
managerRef.current.onLoadFinished
|
|
73
|
+
}
|
|
74
|
+
onLoadFailedFromScreenRevealManager={managerRef.current.onLoadFailed}
|
|
75
|
+
/>
|
|
76
|
+
</Animated.View>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useEffect } from "react";
|
|
2
|
-
import { Animated } from "react-native";
|
|
2
|
+
import { Animated, Dimensions } from "react-native";
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
useSafeAreaInsets,
|
|
@@ -23,6 +23,7 @@ export enum PlayerAnimationStateEnum {
|
|
|
23
23
|
export type PlayerAnimationStateT = number | PlayerAnimationStateEnum | null;
|
|
24
24
|
|
|
25
25
|
export type ModalAnimationContextT = {
|
|
26
|
+
yTranslate: React.MutableRefObject<Animated.Value | null>;
|
|
26
27
|
isActiveGesture: boolean;
|
|
27
28
|
playerAnimationState: PlayerAnimationStateT;
|
|
28
29
|
setPlayerAnimationState: (value: PlayerAnimationStateT) => void;
|
|
@@ -48,6 +49,7 @@ export type ModalAnimationContextT = {
|
|
|
48
49
|
};
|
|
49
50
|
|
|
50
51
|
export const ReactContext = React.createContext<ModalAnimationContextT>({
|
|
52
|
+
yTranslate: React.createRef<Animated.Value | null>(),
|
|
51
53
|
isActiveGesture: false,
|
|
52
54
|
playerAnimationState: null,
|
|
53
55
|
setPlayerAnimationState: () => null,
|
|
@@ -73,6 +75,10 @@ export const ReactContext = React.createContext<ModalAnimationContextT>({
|
|
|
73
75
|
});
|
|
74
76
|
|
|
75
77
|
const Provider = ({ children }: { children: React.ReactNode }) => {
|
|
78
|
+
const yTranslate = React.useRef(
|
|
79
|
+
new Animated.Value(Dimensions.get("window").height)
|
|
80
|
+
);
|
|
81
|
+
|
|
76
82
|
const [playerAnimationState, setPlayerAnimationState] =
|
|
77
83
|
React.useState<PlayerAnimationStateT>(null);
|
|
78
84
|
|
|
@@ -100,6 +106,7 @@ const Provider = ({ children }: { children: React.ReactNode }) => {
|
|
|
100
106
|
// Reset player animation state when video modal is closed
|
|
101
107
|
if (!visible) {
|
|
102
108
|
resetPlayerAnimationState();
|
|
109
|
+
yTranslate.current?.setValue(Dimensions.get("window").height);
|
|
103
110
|
}
|
|
104
111
|
}, [visible, resetPlayerAnimationState]);
|
|
105
112
|
|
|
@@ -141,6 +148,7 @@ const Provider = ({ children }: { children: React.ReactNode }) => {
|
|
|
141
148
|
return (
|
|
142
149
|
<ReactContext.Provider
|
|
143
150
|
value={{
|
|
151
|
+
yTranslate,
|
|
144
152
|
startComponentsAnimation,
|
|
145
153
|
setStartComponentsAnimation,
|
|
146
154
|
isActiveGesture: playerAnimationState !== null,
|
|
@@ -11,6 +11,8 @@ import { useTargetScreenData } from "@applicaster/zapp-react-native-utils/reactH
|
|
|
11
11
|
import { ComponentsMap } from "@applicaster/zapp-react-native-ui-components/Components/River/ComponentsMap";
|
|
12
12
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
13
13
|
import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
14
|
+
import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
15
|
+
import { useDelayedPlayerDetails } from "./hooks";
|
|
14
16
|
|
|
15
17
|
const { width: SCREEN_WIDTH } = Dimensions.get("screen");
|
|
16
18
|
|
|
@@ -26,6 +28,10 @@ type Props = {
|
|
|
26
28
|
isTabletLandscape?: boolean;
|
|
27
29
|
isAudioPlayer?: boolean;
|
|
28
30
|
isTablet?: boolean;
|
|
31
|
+
inline?: any;
|
|
32
|
+
docked?: boolean;
|
|
33
|
+
isModal?: boolean;
|
|
34
|
+
pip?: boolean;
|
|
29
35
|
};
|
|
30
36
|
|
|
31
37
|
const containerStyle = ({
|
|
@@ -42,8 +48,24 @@ export const PlayerDetails = ({
|
|
|
42
48
|
configuration,
|
|
43
49
|
isTabletLandscape = false,
|
|
44
50
|
isAudioPlayer,
|
|
45
|
-
|
|
51
|
+
inline,
|
|
52
|
+
docked,
|
|
53
|
+
isModal,
|
|
54
|
+
pip,
|
|
46
55
|
}: Props) => {
|
|
56
|
+
const isInlineModal = inline && isModal;
|
|
57
|
+
|
|
58
|
+
// Mounting the PlayerDetails component is a resource-intensive process.
|
|
59
|
+
// Therefore, for performance reasons, we mount it with a delay to make the rotation process as smooth as possible.
|
|
60
|
+
// The flow is as follows: the rotation occurs first, and then, after a short delay, we mount the PlayerDetails component.
|
|
61
|
+
// This helps to avoid blocking the rotation and any animations related to the rotation.
|
|
62
|
+
const isShowPlayerDetails = useDelayedPlayerDetails({
|
|
63
|
+
isInline: isInlineModal,
|
|
64
|
+
isDocked: docked,
|
|
65
|
+
isPip: pip,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const isTablet = useIsTablet();
|
|
47
69
|
const screenData = useTargetScreenData(entry);
|
|
48
70
|
const insets = useSafeAreaInsets();
|
|
49
71
|
|
|
@@ -79,7 +101,7 @@ export const PlayerDetails = ({
|
|
|
79
101
|
}
|
|
80
102
|
}, [isAudioPlayer]);
|
|
81
103
|
|
|
82
|
-
if (isNilOrEmpty(screenData?.ui_components)) {
|
|
104
|
+
if (isNilOrEmpty(screenData?.ui_components) || !isShowPlayerDetails) {
|
|
83
105
|
return null;
|
|
84
106
|
}
|
|
85
107
|
|