@applicaster/zapp-react-native-ui-components 12.0.0-alpha.5521005225 → 12.0.0-alpha.5545254270

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 (29) hide show
  1. package/Components/FreezeWithCallback/Freeze.tsx +31 -0
  2. package/Components/FreezeWithCallback/LICENSE +21 -0
  3. package/Components/FreezeWithCallback/Suspender.tsx +17 -0
  4. package/Components/FreezeWithCallback/__tests__/index.test.tsx +170 -0
  5. package/Components/FreezeWithCallback/index.tsx +3 -0
  6. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +1 -7
  7. package/Components/MasterCell/DefaultComponents/ImageBorderContainer/index.ts +71 -0
  8. package/Components/MasterCell/DefaultComponents/index.ts +2 -0
  9. package/Components/MasterCell/SharedUI/ProgressBar/hooks/useFillInPercent.ts +7 -1
  10. package/Components/MasterCell/index.tsx +2 -2
  11. package/Components/Screen/TV/hooks/index.ts +0 -2
  12. package/Components/Screen/TV/index.web.tsx +32 -19
  13. package/Components/Screen/__tests__/Screen.test.tsx +0 -49
  14. package/Components/Screen/index.tsx +1 -19
  15. package/Components/Screen/navigationHandler.ts +1 -3
  16. package/Components/Screen/orientationHandler.ts +18 -7
  17. package/Components/Screen/utils.ts +16 -0
  18. package/Components/Transitioner/Scene.tsx +9 -2
  19. package/Components/Transitioner/Transitioner.tsx +11 -8
  20. package/Components/Transitioner/__tests__/Scene.test.js +7 -0
  21. package/Components/Transitioner/__tests__/__snapshots__/transitioner.test.js.snap +1 -0
  22. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +23 -2
  23. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +5 -1
  24. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +17 -0
  25. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +48 -2
  26. package/Components/ZappFrameworkComponents/BarView/BarView.tsx +4 -3
  27. package/Components/default-cell-renderer/__tests__/__snapshots__/defaultCellRenderer.test.tsx.snap +4 -4
  28. package/package.json +5 -5
  29. package/Components/Screen/TV/hooks/useRestoreFocus.ts +0 -22
@@ -0,0 +1,31 @@
1
+ import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
2
+ import React, { Suspense, useEffect } from "react";
3
+ import { Suspender } from "./Suspender";
4
+
5
+ interface Props {
6
+ freeze: boolean;
7
+ children: React.ReactNode;
8
+ placeholder?: React.ReactNode;
9
+ onRelease?: () => void;
10
+ }
11
+
12
+ export function Freeze({
13
+ freeze,
14
+ children,
15
+ placeholder = null,
16
+ onRelease,
17
+ }: Props) {
18
+ const prevFreeze = usePrevious(freeze);
19
+
20
+ useEffect(() => {
21
+ if (prevFreeze && !freeze && onRelease) {
22
+ onRelease();
23
+ }
24
+ }, [freeze, onRelease, prevFreeze]);
25
+
26
+ return (
27
+ <Suspense fallback={placeholder}>
28
+ <Suspender freeze={freeze}>{children}</Suspender>
29
+ </Suspense>
30
+ );
31
+ }
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Software Mansion
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,17 @@
1
+ import React, { Fragment } from "react";
2
+
3
+ const infiniteThenable = { then() {} };
4
+
5
+ export const Suspender = ({
6
+ freeze,
7
+ children,
8
+ }: {
9
+ freeze: boolean;
10
+ children: React.ReactNode;
11
+ }) => {
12
+ if (freeze) {
13
+ throw infiniteThenable;
14
+ }
15
+
16
+ return <Fragment>{children}</Fragment>;
17
+ };
@@ -0,0 +1,170 @@
1
+ import React, { Dispatch, useEffect, useState } from "react";
2
+ import {
3
+ act,
4
+ create,
5
+ ReactTestRenderer,
6
+ ReactTestRendererJSON,
7
+ } from "react-test-renderer";
8
+ import { FreezeWithCallback } from "../index";
9
+
10
+ const SimpleContent = () => <div />;
11
+
12
+ interface InnerProps {
13
+ value: number;
14
+ }
15
+ // eslint-disable-next-line unused-imports/no-unused-vars
16
+ const Inner = ({ value }: InnerProps) => <div />;
17
+
18
+ interface TestSubscriber {
19
+ renderCount: number;
20
+ subscription: Dispatch<number>;
21
+ }
22
+
23
+ function createSubscriberComponent(): [React.FC, TestSubscriber] {
24
+ const testState: TestSubscriber = {
25
+ renderCount: 0,
26
+ subscription: () => {},
27
+ };
28
+
29
+ const Subscriber: React.FC = () => {
30
+ const [value, setValue] = useState(0);
31
+
32
+ useEffect(() => {
33
+ testState.subscription = setValue;
34
+ }, []);
35
+
36
+ testState.renderCount += 1;
37
+
38
+ return <Inner value={value} />;
39
+ };
40
+
41
+ return [Subscriber, testState];
42
+ }
43
+
44
+ interface ContainerProps {
45
+ freeze: boolean;
46
+ children: React.ReactNode;
47
+ }
48
+
49
+ const Container = ({ freeze, children }: ContainerProps) => (
50
+ <FreezeWithCallback freeze={freeze}>{children}</FreezeWithCallback>
51
+ );
52
+
53
+ function setupTest(initialFreeze: boolean = false) {
54
+ let testRenderer: ReactTestRenderer | undefined;
55
+ const [Subscriber, subscriberState] = createSubscriberComponent();
56
+
57
+ act(() => {
58
+ testRenderer = create(
59
+ <Container freeze={initialFreeze}>
60
+ <Subscriber />
61
+ </Container>
62
+ );
63
+ });
64
+
65
+ return {
66
+ testRenderer,
67
+ testInstance: testRenderer?.root,
68
+ subscriberState,
69
+ updateFreeze: (freeze: boolean) => {
70
+ act(() =>
71
+ testRenderer?.update(
72
+ <Container freeze={freeze}>
73
+ <Subscriber />
74
+ </Container>
75
+ )
76
+ );
77
+ },
78
+ };
79
+ }
80
+
81
+ describe("FreezeWithCallback", () => {
82
+ test("Renders stuff not frozen", () => {
83
+ const testRenderer = create(
84
+ <Container freeze={false}>
85
+ <SimpleContent />
86
+ </Container>
87
+ );
88
+
89
+ expect(testRenderer.root.findByType(SimpleContent)).toBeTruthy();
90
+ });
91
+
92
+ test("Does not render stuff when frozen", () => {
93
+ const testRenderer = create(
94
+ <Container freeze>
95
+ <SimpleContent />
96
+ </Container>
97
+ );
98
+
99
+ expect(testRenderer.root.findAllByType(SimpleContent)).toHaveLength(0);
100
+ });
101
+
102
+ test("Stuff is gone after freeze", () => {
103
+ const testRenderer = create(
104
+ <Container freeze={false}>
105
+ <SimpleContent />
106
+ </Container>
107
+ );
108
+
109
+ expect(testRenderer.root.findByType(SimpleContent)).toBeTruthy();
110
+
111
+ act(() =>
112
+ testRenderer.update(
113
+ <Container freeze>
114
+ <SimpleContent />
115
+ </Container>
116
+ )
117
+ );
118
+
119
+ expect(testRenderer.toJSON()).toBe(null);
120
+ });
121
+
122
+ test("Updates work when not frozen", () => {
123
+ const { testInstance, subscriberState } = setupTest();
124
+
125
+ expect(testInstance?.findByType(Inner).props.value).toEqual(0);
126
+ act(() => subscriberState.subscription(1));
127
+ expect(testInstance?.findByType(Inner).props.value).toEqual(1);
128
+ expect(subscriberState.renderCount).toBe(2);
129
+ });
130
+
131
+ test("Updates does not propagate when frozen", () => {
132
+ const { testInstance, subscriberState, updateFreeze } = setupTest();
133
+
134
+ expect(testInstance?.findByType(Inner).props.value).toEqual(0);
135
+ updateFreeze(true);
136
+ act(() => subscriberState.subscription(1));
137
+ expect(testInstance?.findByType(Inner).props.value).toEqual(0);
138
+ expect(subscriberState.renderCount).toBe(1);
139
+ });
140
+
141
+ test("State persists after defrost", () => {
142
+ const { testInstance, subscriberState, updateFreeze, testRenderer } =
143
+ setupTest();
144
+
145
+ expect(testInstance?.findByType(Inner).props.value).toEqual(0);
146
+ act(() => subscriberState.subscription(1));
147
+ expect(testInstance?.findByType(Inner).props.value).toEqual(1);
148
+
149
+ updateFreeze(true);
150
+ expect(testRenderer?.toJSON()).toBe(null);
151
+
152
+ updateFreeze(false);
153
+ expect((testRenderer?.toJSON() as ReactTestRendererJSON).type).toBe("div");
154
+ expect(testInstance?.findByType(Inner).props.value).toEqual(1);
155
+ });
156
+
157
+ test("Update propagate after defrost", () => {
158
+ const { testInstance, subscriberState, updateFreeze } = setupTest();
159
+
160
+ updateFreeze(true);
161
+ act(() => subscriberState.subscription(1));
162
+ act(() => subscriberState.subscription(2));
163
+ act(() => subscriberState.subscription(3));
164
+ expect(testInstance?.findByType(Inner).props.value).toEqual(0);
165
+
166
+ updateFreeze(false);
167
+ expect(testInstance?.findByType(Inner).props.value).toEqual(3);
168
+ expect(subscriberState.renderCount).toBe(2);
169
+ });
170
+ });
@@ -0,0 +1,3 @@
1
+ import { Freeze } from "./Freeze";
2
+
3
+ export const FreezeWithCallback = Freeze;
@@ -117,9 +117,6 @@ export const BorderContainerView = (props: Props) => {
117
117
  children,
118
118
  } = props;
119
119
 
120
- const borderStyleForMissingBorder =
121
- style?.borderWidth === 0 || !style?.borderColor ? { borderRadius: 0 } : {};
122
-
123
120
  const padding =
124
121
  borderPosition === "outside"
125
122
  ? {
@@ -131,10 +128,7 @@ export const BorderContainerView = (props: Props) => {
131
128
  : {};
132
129
 
133
130
  return (
134
- <View
135
- {...props}
136
- style={[style, styles.container, borderStyleForMissingBorder]}
137
- >
131
+ <View pointerEvents="box-none" {...props} style={[style, styles.container]}>
138
132
  <Border style={style} padding={padding} borderPosition={borderPosition} />
139
133
  {children}
140
134
  </View>
@@ -0,0 +1,71 @@
1
+ import { ImageStyle } from "react-native";
2
+
3
+ import {
4
+ getImageContainerMarginStyles,
5
+ ifElseFocusedSelected,
6
+ } from "@applicaster/zapp-react-native-utils/cellUtils";
7
+ import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
8
+
9
+ export const ImageBorderContainer = ({
10
+ value,
11
+ state,
12
+ imageStyles,
13
+ }: {
14
+ value: (key: string) => string;
15
+ state: CellState;
16
+ imageStyles: ImageStyle;
17
+ }) => {
18
+ const imageMargins = getImageContainerMarginStyles({ value });
19
+
20
+ return {
21
+ type: "BorderContainerView",
22
+ style: {
23
+ position: "absolute",
24
+ // Image border now separated from the image, so cell padding affects the border position
25
+ marginTop:
26
+ toNumberWithDefaultZero(imageMargins.marginTop) +
27
+ toNumberWithDefaultZero(value("cell_padding_top")),
28
+ marginLeft:
29
+ toNumberWithDefaultZero(imageMargins.marginLeft) +
30
+ toNumberWithDefaultZero(value("cell_padding_left")),
31
+ marginRight:
32
+ toNumberWithDefaultZero(imageMargins.marginRight) +
33
+ toNumberWithDefaultZero(value("cell_padding_right")),
34
+ marginBottom:
35
+ toNumberWithDefaultZero(imageMargins.marginBottom) +
36
+ toNumberWithDefaultZero(value("cell_padding_bottom")),
37
+ zIndex: 1,
38
+ borderWidth: value("image_border_size"),
39
+ borderColor: ifElseFocusedSelected(
40
+ state,
41
+ value("image_focused_border_color"),
42
+ value("image_selected_border_color"),
43
+ value("image_focused_selected_border_color"),
44
+ value("image_border_color")
45
+ ),
46
+ borderRadius: toNumberWithDefaultZero(value("image_corner_radius")),
47
+ },
48
+ additionalProps: {
49
+ pointerEvents: "none",
50
+ borderPosition: value("image_border_position"),
51
+ borderPaddingTop: toNumberWithDefaultZero(
52
+ value("image_border_padding_top")
53
+ ),
54
+ borderPaddingRight: toNumberWithDefaultZero(
55
+ value("image_border_padding_right")
56
+ ),
57
+ borderPaddingBottom: toNumberWithDefaultZero(
58
+ value("image_border_padding_bottom")
59
+ ),
60
+ borderPaddingLeft: toNumberWithDefaultZero(
61
+ value("image_border_padding_left")
62
+ ),
63
+ },
64
+ elements: [
65
+ {
66
+ type: "View",
67
+ style: imageStyles,
68
+ },
69
+ ],
70
+ };
71
+ };
@@ -15,6 +15,7 @@ import { BorderContainerView } from "./BorderContainerView";
15
15
  import { CellBadge } from "./CellBadge";
16
16
  import { TvActionButtons } from "./tv/TvActionButtons";
17
17
  import { ButtonContainerView } from "./tv/ButtonContainerView";
18
+ import { ImageBorderContainer } from "./ImageBorderContainer";
18
19
 
19
20
  export const defaultComponents = {
20
21
  View,
@@ -28,6 +29,7 @@ export const defaultComponents = {
28
29
  ProgressBar,
29
30
  Toggle,
30
31
  LiveImage,
32
+ ImageBorderContainer,
31
33
  BorderContainerView,
32
34
  DynamicBadge,
33
35
  SecondaryImage,
@@ -5,6 +5,7 @@ import * as R from "ramda";
5
5
 
6
6
  import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
7
7
  import { normalizeProgressValue } from "./utils";
8
+ import { useIsScreenActive } from "@applicaster/zapp-react-native-utils/reactHooks";
8
9
 
9
10
  const ACTION_NAME = "continue-watching";
10
11
  const DEFAULT_FILL = 0;
@@ -27,6 +28,7 @@ const getProgressValue = (
27
28
 
28
29
  export const useFillInPercent = (entry: ZappEntry): number => {
29
30
  const action = useActions(ACTION_NAME);
31
+ const isActive = useIsScreenActive();
30
32
 
31
33
  const [fillInPercent, setFillInPercent] =
32
34
  React.useState<number>(DEFAULT_FILL);
@@ -39,6 +41,10 @@ export const useFillInPercent = (entry: ZappEntry): number => {
39
41
  }, []);
40
42
 
41
43
  React.useEffect(() => {
44
+ if (!isActive) {
45
+ return;
46
+ }
47
+
42
48
  // refresh progress in UI when it was changed in CW
43
49
  const unsubscribe = action?.addDataSourceListener?.(() => {
44
50
  getProgressValue(action, entry, (progress) => {
@@ -49,7 +55,7 @@ export const useFillInPercent = (entry: ZappEntry): number => {
49
55
  return () => {
50
56
  unsubscribe?.();
51
57
  };
52
- }, []);
58
+ }, [isActive]);
53
59
 
54
60
  return fillInPercent;
55
61
  };
@@ -76,8 +76,8 @@ export function masterCellBuilder({
76
76
  const selectedState = screen?.selectedEntry;
77
77
 
78
78
  const entryIsSelected = React.useMemo(
79
- () => selectedState?.id === item?.id,
80
- [selectedState?.id]
79
+ () => !!(selectedState?.id && selectedState?.id === item?.id),
80
+ [selectedState?.id, item?.id]
81
81
  );
82
82
 
83
83
  const elements = React.useMemo(
@@ -1,3 +1 @@
1
1
  export { useInitialFocus } from "./useInitialFocus";
2
-
3
- export { useRestoreFocus } from "./useRestoreFocus";
@@ -1,12 +1,15 @@
1
- import * as React from "react";
1
+ import React from "react";
2
2
  import { last } from "ramda";
3
- import { View, StyleSheet } from "react-native";
3
+ import { StyleSheet, View } from "react-native";
4
4
  import {
5
5
  findMenuPlugin,
6
6
  getNavigationProps,
7
7
  getPathAttributes,
8
8
  } from "@applicaster/zapp-react-native-utils/navigationUtils";
9
- import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks";
9
+ import {
10
+ useIsScreenActive,
11
+ useNavigation,
12
+ } from "@applicaster/zapp-react-native-utils/reactHooks";
10
13
  import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
11
14
  import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
12
15
  import {
@@ -17,8 +20,10 @@ import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/he
17
20
 
18
21
  import { NavBarContainer } from "../../Layout/TV/NavBarContainer";
19
22
  import { ScreenResolver } from "../../ScreenResolver";
20
- import { useInitialFocus, useRestoreFocus } from "./hooks";
23
+ import { useInitialFocus } from "./hooks";
21
24
  import { isPlayerPlugin } from "@applicaster/zapp-react-native-utils/pluginUtils";
25
+ import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils";
26
+ import { FreezeWithCallback } from "../../FreezeWithCallback";
22
27
 
23
28
  const styles = StyleSheet.create({
24
29
  container: {
@@ -103,7 +108,6 @@ export const Screen = ({ route, Components }: Props) => {
103
108
  const backgroundColor = useScreenBackgroundColor(screenId);
104
109
 
105
110
  useInitialFocus();
106
- useRestoreFocus();
107
111
 
108
112
  const activeRiver = getActiveRiver({
109
113
  screenId,
@@ -145,21 +149,30 @@ export const Screen = ({ route, Components }: Props) => {
145
149
  ? routeState?.state?.entry
146
150
  : routeState?.state?.screen;
147
151
 
152
+ const isScreenActive = useIsScreenActive();
153
+
148
154
  return (
149
- <View style={[styles.container, { backgroundColor }]}>
150
- <NavBarContainer isVisible={isNavBarVisible} onReady={noop}>
151
- <NavBar
152
- isVisible={isNavBarVisible}
153
- menuPlugin={menuPlugin}
154
- navigationProps={navigationProps}
155
+ <FreezeWithCallback
156
+ freeze={!isScreenActive}
157
+ onRelease={() => {
158
+ focusManager.setInitialFocus();
159
+ }}
160
+ >
161
+ <View style={[styles.container, { backgroundColor }]}>
162
+ <NavBarContainer isVisible={isNavBarVisible} onReady={noop}>
163
+ <NavBar
164
+ isVisible={isNavBarVisible}
165
+ menuPlugin={menuPlugin}
166
+ navigationProps={navigationProps}
167
+ />
168
+ </NavBarContainer>
169
+ <ScreenResolver
170
+ screenType={screenType}
171
+ screenId={screenId}
172
+ screenData={screenData}
173
+ groupId={route}
155
174
  />
156
- </NavBarContainer>
157
- <ScreenResolver
158
- screenType={screenType}
159
- screenId={screenId}
160
- screenData={screenData}
161
- groupId={route}
162
- />
163
- </View>
175
+ </View>
176
+ </FreezeWithCallback>
164
177
  );
165
178
  };
@@ -99,14 +99,6 @@ jest.mock("@applicaster/zapp-react-native-redux/hooks", () => ({
99
99
  }),
100
100
  }));
101
101
 
102
- const {
103
- ORIENTATIONS,
104
- } = require("@applicaster/zapp-react-native-utils/appUtils/orientationHelper");
105
-
106
- const {
107
- useCurrentScreenData,
108
- } = require("@applicaster/zapp-react-native-utils/reactHooks/screen");
109
-
110
102
  const {
111
103
  allowedOrientationsForScreen,
112
104
  getOrientation,
@@ -158,45 +150,4 @@ describe("<Screen Component />", () => {
158
150
  expect(wrapper.toJSON()).toMatchSnapshot();
159
151
  });
160
152
  });
161
-
162
- it("invokes the orientation hook when the route changes", () => {
163
- act(() => {
164
- wrapper = create(<Screen {...screenProps} />);
165
- });
166
-
167
- // @ts-ignore
168
- useCurrentScreenData.mockReturnValue({
169
- id: "player-testId",
170
- general: { screen_orientation: ORIENTATIONS.landscapeSensor },
171
- plugin_type: "player",
172
- });
173
-
174
- act(() => {
175
- wrapper.update(
176
- <Screen
177
- {...screenProps}
178
- pathname="/playable"
179
- screenData={{
180
- id: "player-testId",
181
- general: { screen_orientation: ORIENTATIONS.landscapeSensor },
182
- plugin_type: "player",
183
- }}
184
- />
185
- );
186
- });
187
-
188
- useCurrentScreenData.mockReturnValue({
189
- id: "player-testId",
190
- general: { screen_orientation: ORIENTATIONS.landscapeSensor },
191
- });
192
-
193
- act(() => {
194
- wrapper.update(<Screen {...screenProps} />);
195
- });
196
-
197
- expect(allowedOrientationsForScreen).toHaveBeenNthCalledWith(
198
- 1,
199
- ORIENTATIONS.landscapeSensor
200
- );
201
- });
202
153
  });
@@ -1,6 +1,5 @@
1
1
  /// <reference types="@applicaster/applicaster-types" />
2
2
  import React from "react";
3
- import * as R from "ramda";
4
3
  import { View } from "react-native";
5
4
  import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
6
5
  import {
@@ -27,7 +26,7 @@ import { getNavigationPluginModule } from "@applicaster/zapp-react-native-app/Ap
27
26
 
28
27
  import { RouteManager } from "../RouteManager";
29
28
  import { useScreenConfiguration } from "../River/useScreenConfiguration";
30
- import { useScreenOrientationHandler } from "./orientationHandler";
29
+ import { isValidColor } from "./utils";
31
30
 
32
31
  const screenStyles = {
33
32
  flex: 1,
@@ -37,21 +36,6 @@ const screenStyles = {
37
36
  // paddingRight: insets.right, // Should be applied directly by UI plugin
38
37
  };
39
38
 
40
- function colorIsNotTransparent(color) {
41
- const layers = R.compose(
42
- R.map(R.trim),
43
- R.split(","),
44
- R.replace(")", ""),
45
- R.replace("rgba(", "")
46
- )(color);
47
-
48
- return Number(layers?.[3]) > 0;
49
- }
50
-
51
- function isValidColor(string) {
52
- return string && string !== "transparent" && colorIsNotTransparent(string);
53
- }
54
-
55
39
  type Props = {
56
40
  pathname: unknown;
57
41
  screenData: unknown;
@@ -127,8 +111,6 @@ export function Screen(_props: Props) {
127
111
  const targetScreenData =
128
112
  currentScreenData || (screenData as any)?.targetScreen || screenData;
129
113
 
130
- useScreenOrientationHandler({ screenData: targetScreenData });
131
-
132
114
  const orientation = useGetScreenOrientation(targetScreenData);
133
115
 
134
116
  // We prevent rendering of the screen until UI is actually rotated to screen desired orientation.
@@ -26,9 +26,7 @@ export function isMenuVisible(route, screenData) {
26
26
  if (screenData?.hookPlugin) {
27
27
  const hookPlugin = screenData?.hookPlugin?.module;
28
28
 
29
- return (
30
- hookPlugin?.showNavBar || hookPlugin?.presentFullScreen !== true || false
31
- );
29
+ return hookPlugin?.showNavBar || hookPlugin?.presentFullScreen !== true;
32
30
  }
33
31
 
34
32
  if (
@@ -1,14 +1,16 @@
1
1
  import React, { useLayoutEffect } from "react";
2
+
2
3
  import {
3
4
  allowedOrientationsForScreen,
4
5
  ORIENTATIONS,
5
6
  getOrientation,
6
7
  useGetScreenOrientation,
7
8
  } from "@applicaster/zapp-react-native-utils/appUtils/orientationHelper";
8
-
9
+ import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
9
10
  import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
10
11
  import { findPluginByType } from "@applicaster/zapp-react-native-utils/pluginUtils";
11
12
  import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
13
+
12
14
  import { ZappHookModalContext } from "../../Contexts";
13
15
  import { HookModalContextT } from "../../Contexts/ZappHookModalContext";
14
16
 
@@ -79,14 +81,23 @@ export function useNewOrientationForScreenData({
79
81
  screenOrientation;
80
82
  }
81
83
 
82
- export function useScreenOrientationHandler({
83
- screenData,
84
- }: OrientationHookArgs) {
84
+ type Props = {
85
+ screenData: {
86
+ screen: {};
87
+ };
88
+ isActive: boolean;
89
+ };
90
+
91
+ export function useScreenOrientationHandler({ screenData, isActive }: Props) {
85
92
  const { isHooksExecutionInProgress } = React.useContext<HookModalContextT>(
86
93
  ZappHookModalContext.ReactContext
87
94
  );
88
95
 
89
- const newOrientation = useNewOrientationForScreenData({ screenData });
96
+ const prevIsActive = usePrevious(isActive);
97
+
98
+ const newOrientation = useNewOrientationForScreenData({
99
+ screenData: screenData.screen,
100
+ });
90
101
 
91
102
  useLayoutEffect(() => {
92
103
  // Null means screen does not have orientation preference, so we can skip rotate
@@ -100,12 +111,12 @@ export function useScreenOrientationHandler({
100
111
  return;
101
112
  }
102
113
 
103
- if (newOrientation !== current) {
114
+ if (!prevIsActive && isActive && newOrientation !== current) {
104
115
  current = newOrientation;
105
116
 
106
117
  setOrientation(newOrientation);
107
118
  }
108
- }, [newOrientation, isHooksExecutionInProgress]);
119
+ }, [newOrientation, isHooksExecutionInProgress, prevIsActive, isActive]);
109
120
  }
110
121
 
111
122
  export function useOrientationHandler({ screenData }: OrientationHookArgs) {
@@ -0,0 +1,16 @@
1
+ import { map, split, replace, compose, trim } from "ramda";
2
+
3
+ function colorIsNotTransparent(color) {
4
+ const layers = compose(
5
+ map(trim),
6
+ split(","),
7
+ replace(")", ""),
8
+ replace("rgba(", "")
9
+ )(color);
10
+
11
+ return Number(layers?.[3]) > 0;
12
+ }
13
+
14
+ export function isValidColor(string) {
15
+ return string && string !== "transparent" && colorIsNotTransparent(string);
16
+ }
@@ -1,6 +1,9 @@
1
1
  import React from "react";
2
2
  import { Animated, ViewProps, ViewStyle } from "react-native";
3
3
  import { useSafeAreaFrame } from "react-native-safe-area-context";
4
+
5
+ import { useScreenOrientationHandler } from "@applicaster/zapp-react-native-ui-components/Components/Screen/orientationHandler";
6
+
4
7
  import { PathnameContext } from "../../Contexts/PathnameContext";
5
8
  import { ScreenDataContext } from "../../Contexts/ScreenDataContext";
6
9
  import { ScreenContextProvider } from "../../Contexts/ScreenContext";
@@ -42,6 +45,11 @@ export function Scene({
42
45
  }: Props) {
43
46
  const [initialScreenData, setInitialScreenData] = React.useState(screenData);
44
47
 
48
+ useScreenOrientationHandler({
49
+ screenData,
50
+ isActive,
51
+ });
52
+
45
53
  React.useLayoutEffect(() => {
46
54
  if (isActive) {
47
55
  setInitialScreenData(screenData);
@@ -49,13 +57,12 @@ export function Scene({
49
57
  }, [screenData, isActive]);
50
58
 
51
59
  const frame = useSafeAreaFrame();
52
-
53
60
  const isAnimating = animating && overlayStyle;
54
61
 
55
62
  const dimensions = isAnimating
56
63
  ? {
57
- height: "100%",
58
64
  width: "100%",
65
+ height: "100%",
59
66
  }
60
67
  : frame;
61
68
 
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
  import { View } from "react-native";
3
- import * as R from "ramda";
3
+ import { update, compose, tail, append, take, identity } from "ramda";
4
4
 
5
5
  import { AnimationManager } from "./AnimationManager";
6
6
  import { Scene } from "./Scene";
@@ -46,7 +46,7 @@ function prepareScene(children) {
46
46
  */
47
47
  function scenesForBackTransition(props: SceneProps, { scenes }) {
48
48
  return scenes.length > 1
49
- ? R.identity(scenes)
49
+ ? identity(scenes)
50
50
  : [prepareScene(props.children), scenes[0]];
51
51
  }
52
52
 
@@ -110,7 +110,7 @@ export class TransitionerComponent extends React.PureComponent<Props, State> {
110
110
  pushTransitionStart() {
111
111
  this.setState(
112
112
  {
113
- scenes: R.append(prepareScene(this.props.children), this.state.scenes),
113
+ scenes: append(prepareScene(this.props.children), this.state.scenes),
114
114
  ...this.animationManager.stateForAction(this.state, NAV_ACTION_PUSH),
115
115
  },
116
116
  () => {
@@ -177,10 +177,10 @@ export class TransitionerComponent extends React.PureComponent<Props, State> {
177
177
  to: { index: 0 },
178
178
  };
179
179
  } else if (isLastSceneHook || scenes.length === 1) {
180
- newState = { scenes: R.update(scenes.length - 1, newScene, scenes) };
180
+ newState = { scenes: update(scenes.length - 1, newScene, scenes) };
181
181
  } else {
182
182
  newState = {
183
- scenes: R.compose(R.tail, R.append(newScene))(scenes),
183
+ scenes: compose(tail, append(newScene))(scenes),
184
184
  };
185
185
  }
186
186
 
@@ -225,7 +225,10 @@ export class TransitionerComponent extends React.PureComponent<Props, State> {
225
225
  this.shouldSkipAnimationForRoute(previousRoute) ||
226
226
  this.shouldSkipAnimationForRoute(currentRoute)
227
227
  ) {
228
- return this.transitionWithoutAnimation();
228
+ // we are going back, in this case we need to have only one scene using resetState
229
+ const resetState = isGoingBack ? true : undefined;
230
+
231
+ return this.transitionWithoutAnimation(resetState);
229
232
  }
230
233
 
231
234
  switch (this.props.navigator.previousAction) {
@@ -268,7 +271,7 @@ export class TransitionerComponent extends React.PureComponent<Props, State> {
268
271
  backTransitionEnd(stepsBack) {
269
272
  this.setState({
270
273
  animating: false,
271
- scenes: R.take(this.state.scenes.length - stepsBack, this.state.scenes),
274
+ scenes: take(this.state.scenes.length - stepsBack, this.state.scenes),
272
275
  });
273
276
  }
274
277
 
@@ -284,7 +287,7 @@ export class TransitionerComponent extends React.PureComponent<Props, State> {
284
287
  <View style={styles.container}>
285
288
  {scenes[to.index] && (
286
289
  <Scene
287
- {...{ style: to.style }}
290
+ {...{ style: to?.style || {} }}
288
291
  pathname={pathname}
289
292
  isActive={pathname === this.props.navigator.currentRoute}
290
293
  contentStyle={contentStyle}
@@ -16,6 +16,13 @@ jest.mock("react-native-safe-area-context", () => ({
16
16
  })),
17
17
  }));
18
18
 
19
+ jest.mock(
20
+ "@applicaster/zapp-react-native-ui-components/Components/Screen/orientationHandler",
21
+ () => ({
22
+ useScreenOrientationHandler: jest.fn(),
23
+ })
24
+ );
25
+
19
26
  const { Scene } = require("../Scene");
20
27
 
21
28
  const wrapper = shallow(
@@ -12,6 +12,7 @@ exports[`<TransitionerComponent /> renders correctly 1`] = `
12
12
  <Scene
13
13
  isActive={true}
14
14
  screenUniqueId="mocked-uuid"
15
+ style={Object {}}
15
16
  >
16
17
  <View
17
18
  foo="bar"
@@ -200,7 +200,23 @@ export const AnimatedScrollModalComponent = ({ children }: Props) => {
200
200
  videoModalState.mode === "MINIMIZED" &&
201
201
  videoModalState.previousMode === "MAXIMIZED"
202
202
  ) {
203
- resetPlayerAnimationState();
203
+ // set animation to the minimize values if moving from the player to another screen
204
+ if (playerAnimationState === null) {
205
+ setScrollModalAnimatedValue(
206
+ translateYOffset,
207
+ modalSnapPoints[1],
208
+ setLastSnap
209
+ );
210
+
211
+ resetScrollAnimatedValues(
212
+ lastScrollY,
213
+ lastScrollYValue,
214
+ dragScrollY,
215
+ dragVideoPlayerY
216
+ );
217
+ } else {
218
+ resetPlayerAnimationState();
219
+ }
204
220
  }
205
221
 
206
222
  if (
@@ -349,7 +365,12 @@ export const AnimatedScrollModalComponent = ({ children }: Props) => {
349
365
  };
350
366
 
351
367
  export const AnimatedScrollModal = ({ children }: Props) => {
352
- const Component = !isTV() ? AnimatedScrollModalComponent : React.Fragment;
368
+ const {
369
+ videoModalState: { visible },
370
+ } = useNavigation();
371
+
372
+ const Component =
373
+ !isTV() && visible ? AnimatedScrollModalComponent : React.Fragment;
353
374
 
354
375
  return <Component>{children}</Component>;
355
376
  };
@@ -155,7 +155,11 @@ export const AnimatedVideoPlayer = ({ children }: Props) => {
155
155
  };
156
156
 
157
157
  export const AnimatedVideoPlayerComponent = ({ children }: Props) => {
158
- const Component = !isTV() ? AnimatedVideoPlayer : React.Fragment;
158
+ const {
159
+ videoModalState: { visible },
160
+ } = useNavigation();
161
+
162
+ const Component = !isTV() && visible ? AnimatedVideoPlayer : React.Fragment;
159
163
 
160
164
  return <Component>{children}</Component>;
161
165
  };
@@ -309,6 +309,23 @@ export const AnimationView = ({
309
309
  }
310
310
  }, [additionalData, screenWidth]);
311
311
 
312
+ React.useEffect(() => {
313
+ if (
314
+ videoModalState.mode === "MINIMIZED" &&
315
+ videoModalState.previousMode === "MAXIMIZED"
316
+ ) {
317
+ // set animation to the minimize values if moving from the player to another screen
318
+ if (playerAnimationState === null) {
319
+ const value = getAnimationValue(
320
+ animationType,
321
+ PlayerAnimationStateEnum.minimize
322
+ ).toValue;
323
+
324
+ animatedValue.setValue(value);
325
+ }
326
+ }
327
+ }, [videoModalState]);
328
+
312
329
  React.useEffect(() => {
313
330
  if (
314
331
  playerAnimationState === PlayerAnimationStateEnum.minimize &&
@@ -72,12 +72,34 @@ exports[`PlayerWrapper renders inline 1`] = `
72
72
  testID="test-player-container"
73
73
  />
74
74
  <View
75
+ animationType="componentFade"
75
76
  style={
76
77
  Object {
77
78
  "flex": 1,
78
79
  }
79
80
  }
80
- />
81
+ >
82
+ <View
83
+ configuration={
84
+ Object {
85
+ "tablet_landscape_player_container_background_color": "red",
86
+ "tablet_landscape_sidebar_width": "35%",
87
+ }
88
+ }
89
+ entry={
90
+ Object {
91
+ "id": "test",
92
+ }
93
+ }
94
+ isTabletLandscape={false}
95
+ style={
96
+ Object {
97
+ "flex": 1,
98
+ "paddingTop": 20,
99
+ }
100
+ }
101
+ />
102
+ </View>
81
103
  </View>
82
104
  </RNCSafeAreaView>
83
105
  </RNCSafeAreaProvider>
@@ -158,6 +180,7 @@ exports[`PlayerWrapper renders inline and docked 1`] = `
158
180
  style={
159
181
  Object {
160
182
  "flex": 1,
183
+ "paddingTop": 20,
161
184
  }
162
185
  }
163
186
  />
@@ -254,12 +277,34 @@ exports[`PlayerWrapper renders inline on tablet in landscape orientation 1`] = `
254
277
  />
255
278
  </View>
256
279
  <View
280
+ animationType="componentFade"
257
281
  style={
258
282
  Object {
259
283
  "flex": 1,
260
284
  }
261
285
  }
262
- />
286
+ >
287
+ <View
288
+ configuration={
289
+ Object {
290
+ "tablet_landscape_player_container_background_color": "red",
291
+ "tablet_landscape_sidebar_width": "35%",
292
+ }
293
+ }
294
+ entry={
295
+ Object {
296
+ "id": "test",
297
+ }
298
+ }
299
+ isTabletLandscape={true}
300
+ style={
301
+ Object {
302
+ "flex": 1,
303
+ "paddingTop": 20,
304
+ }
305
+ }
306
+ />
307
+ </View>
263
308
  </View>
264
309
  </RNCSafeAreaView>
265
310
  </RNCSafeAreaProvider>
@@ -340,6 +385,7 @@ exports[`PlayerWrapper renders properly 1`] = `
340
385
  style={
341
386
  Object {
342
387
  "flex": 1,
388
+ "paddingTop": 20,
343
389
  }
344
390
  }
345
391
  />
@@ -1,5 +1,6 @@
1
1
  import React from "react";
2
- import { View, StyleSheet } from "react-native";
2
+ import { StyleSheet } from "react-native";
3
+ import { SafeAreaView } from "react-native-safe-area-context";
3
4
 
4
5
  import { useIsRTL } from "@applicaster/zapp-react-native-utils/localizationUtils";
5
6
  import CloseButton from "./Buttons/CloseButton";
@@ -44,11 +45,11 @@ const BarView = ({ screenStyles, barState = defaultState }: Props) => {
44
45
  );
45
46
 
46
47
  return (
47
- <View style={[style.view, isRTL ? style.rtlStyle : {}]}>
48
+ <SafeAreaView style={[style.view, isRTL ? style.rtlStyle : {}]}>
48
49
  {backButton}
49
50
  <BarTitle title={barState.title} screenStyles={screenStyles} />
50
51
  {closeButton}
51
- </View>
52
+ </SafeAreaView>
52
53
  );
53
54
  };
54
55
 
@@ -16,9 +16,9 @@ exports[`DefaultCellRenderer returns a react component and it renders correctly
16
16
  store={null}
17
17
  style={
18
18
  Object {
19
- "backgroundColor": "#CCC",
19
+ "backgroundColor": "transparent",
20
20
  "margin": 12,
21
- "opacity": 0.6,
21
+ "opacity": 1,
22
22
  "width": 169,
23
23
  }
24
24
  }
@@ -105,7 +105,7 @@ exports[`DefaultCellRenderer returns a react component for LG TVs 1`] = `
105
105
  store={null}
106
106
  style={
107
107
  Object {
108
- "backgroundColor": "rgba(252,70,27,1.0)",
108
+ "backgroundColor": "rgba(47,47,47,1.0)",
109
109
  "borderRadius": 4,
110
110
  "flex": 1,
111
111
  "overflow": "hidden",
@@ -238,7 +238,7 @@ exports[`DefaultCellRenderer returns a react component for Samsung TVs 1`] = `
238
238
  store={null}
239
239
  style={
240
240
  Object {
241
- "backgroundColor": "rgba(252,70,27,1.0)",
241
+ "backgroundColor": "rgba(47,47,47,1.0)",
242
242
  "borderRadius": 4,
243
243
  "flex": 1,
244
244
  "overflow": "hidden",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-ui-components",
3
- "version": "12.0.0-alpha.5521005225",
3
+ "version": "12.0.0-alpha.5545254270",
4
4
  "description": "Applicaster Zapp React Native ui components for the Quick Brick App",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -34,10 +34,10 @@
34
34
  "redux-mock-store": "^1.5.3"
35
35
  },
36
36
  "dependencies": {
37
- "@applicaster/applicaster-types": "12.0.0-alpha.5521005225",
38
- "@applicaster/zapp-react-native-bridge": "12.0.0-alpha.5521005225",
39
- "@applicaster/zapp-react-native-redux": "12.0.0-alpha.5521005225",
40
- "@applicaster/zapp-react-native-utils": "12.0.0-alpha.5521005225",
37
+ "@applicaster/applicaster-types": "12.0.0-alpha.5545254270",
38
+ "@applicaster/zapp-react-native-bridge": "12.0.0-alpha.5545254270",
39
+ "@applicaster/zapp-react-native-redux": "12.0.0-alpha.5545254270",
40
+ "@applicaster/zapp-react-native-utils": "12.0.0-alpha.5545254270",
41
41
  "promise": "^8.3.0",
42
42
  "react-router-native": "^5.1.2",
43
43
  "url": "^0.11.0",
@@ -1,22 +0,0 @@
1
- import * as React from "react";
2
- import { isNil } from "ramda";
3
- import { useIsScreenActive } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
4
- import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
5
- import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
6
-
7
- export const useRestoreFocus = (): void => {
8
- const isScreenActive = useIsScreenActive();
9
- const previousIsScreenActive = usePrevious(isScreenActive);
10
-
11
- React.useEffect(() => {
12
- if (isNil(previousIsScreenActive)) {
13
- // it's initial mounting of the screen, do nothing here
14
- return;
15
- }
16
-
17
- if (!previousIsScreenActive && isScreenActive) {
18
- // restore focus when we go back from next screen
19
- focusManager.setInitialFocus();
20
- }
21
- }, [previousIsScreenActive, isScreenActive]);
22
- };