@applicaster/zapp-react-native-ui-components 12.0.0-alpha.5208030571 → 12.0.0-alpha.5227180498

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 (27) hide show
  1. package/Components/BaseFocusable/index.tsx +1 -1
  2. package/Components/Cell/Cell.tsx +1 -5
  3. package/Components/Cell/CellWithFocusable.tsx +5 -17
  4. package/Components/FocusableGroup/index.tsx +0 -1
  5. package/Components/HandlePlayable/HandlePlayable.tsx +0 -3
  6. package/Components/Layout/TV/ScreenLayoutContextProvider.tsx +2 -2
  7. package/Components/Layout/TV/index.tsx +25 -124
  8. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +2 -0
  9. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -1
  10. package/Components/MasterCell/DefaultComponents/Text/index.tsx +14 -2
  11. package/Components/MasterCell/DefaultComponents/__tests__/text.test.tsx +24 -9
  12. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/index.ts +1 -3
  13. package/Components/MasterCell/hooks/useAsyncRendering/index.ts +1 -1
  14. package/Components/MeasurmentsPortal/BasicMeasurementsPortal.tsx +11 -7
  15. package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +10 -1
  16. package/Components/PlayerContainer/PlayerContainer.tsx +2 -3
  17. package/Components/River/ComponentsMap/ComponentsMap.tsx +18 -17
  18. package/Components/River/RefreshControl.tsx +55 -1
  19. package/Components/River/TV/River.tsx +1 -0
  20. package/Components/ScreenResolver/index.tsx +1 -3
  21. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +13 -2
  22. package/Components/VideoModal/VideoModal.tsx +19 -19
  23. package/Contexts/ScreenContext/index.tsx +2 -2
  24. package/Contexts/ZappPipesContext/ZappPipesEntryContext.ts +1 -1
  25. package/events/index.ts +1 -0
  26. package/package.json +5 -5
  27. package/Components/Screen/TV/index.web.tsx +0 -151
@@ -73,7 +73,7 @@ export class BaseFocusable<
73
73
  getId(props?: { id: string; groupId: string }) {
74
74
  const { id, groupId = null } = props || this.props;
75
75
 
76
- return groupId ? `groupId:${groupId}___${id}` : id;
76
+ return groupId ? `${groupId}-${id}` : id;
77
77
  }
78
78
 
79
79
  getRect() {
@@ -207,11 +207,7 @@ export class CellComponent extends React.Component<Props> {
207
207
 
208
208
  const { id } = item;
209
209
 
210
- const focusableId = R.join("-", [
211
- `componentId:${component?.id}`,
212
- `id:${id}`,
213
- `index:${index}`,
214
- ]);
210
+ const focusableId = R.join("-", [component?.id, id, index]);
215
211
 
216
212
  const handleFocus = (focusable, mouse) => {
217
213
  const focusFn = onFocus || noop;
@@ -6,20 +6,12 @@ import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
6
6
  import { isAppleTV } from "../../Helpers/Platform";
7
7
  import { useCellState } from "../MasterCell/utils";
8
8
 
9
- const useCellFocusedState = (
10
- skipFocusManagerRegistration: boolean,
11
- groupId: string,
12
- id: string
13
- ) => {
9
+ const useCellFocusedState = (groupId: string, id: string) => {
14
10
  const [currentCellFocused, setCurrentCellFocused] = React.useState(false);
15
11
 
16
12
  React.useEffect(() => {
17
- const isGroupItemFocused = () => {
18
- if (!skipFocusManagerRegistration) {
19
- const isFocused = focusManager.isGroupItemFocused(groupId, id);
20
- setCurrentCellFocused(isFocused);
21
- }
22
- };
13
+ const isGroupItemFocused = () =>
14
+ setCurrentCellFocused(focusManager.isGroupItemFocused(groupId, id));
23
15
 
24
16
  const handler = () => {
25
17
  // tvOS hack for properly checking focus
@@ -37,7 +29,7 @@ const useCellFocusedState = (
37
29
  return () => {
38
30
  focusManager.removeHandler(FOCUS_EVENTS.FOCUS, handler);
39
31
  };
40
- }, [groupId, skipFocusManagerRegistration]);
32
+ }, [groupId]);
41
33
 
42
34
  return currentCellFocused;
43
35
  };
@@ -73,11 +65,7 @@ export function CellWithFocusable(props: Props) {
73
65
  selected,
74
66
  } = props;
75
67
 
76
- const isFocused = useCellFocusedState(
77
- skipFocusManagerRegistration,
78
- groupId,
79
- id
80
- );
68
+ const isFocused = useCellFocusedState(groupId, id);
81
69
 
82
70
  const state = useCellState({
83
71
  id: item.id,
@@ -26,7 +26,6 @@ type Props = {
26
26
  nextFocusLeft?: any;
27
27
  nextFocusRight?: any;
28
28
  onLayout?: any;
29
- excludeFromFocusSearching?: boolean;
30
29
  };
31
30
 
32
31
  export class FocusableGroup extends BaseFocusable<Props> {
@@ -19,7 +19,6 @@ type Props = {
19
19
  modalHeight?: number;
20
20
  modalWidth?: number;
21
21
  mode: VideoModalMode;
22
- groupId?: string;
23
22
  };
24
23
 
25
24
  const YOUTUBE_PLUGIN_ID = "youtube-player-qb";
@@ -94,7 +93,6 @@ export function HandlePlayable({
94
93
  modalHeight,
95
94
  modalWidth,
96
95
  mode,
97
- groupId,
98
96
  }: Props): React.ReactElement | null {
99
97
  const state = usePickFromState();
100
98
 
@@ -169,7 +167,6 @@ export function HandlePlayable({
169
167
  isModal={isModal}
170
168
  mode={mode}
171
169
  disableCastAction={playerModuleProperties?.disableCastAction}
172
- groupId={groupId}
173
170
  />
174
171
  );
175
172
  }
@@ -60,7 +60,7 @@ export function ScreenLayoutContextProvider(props: {
60
60
  initialState
61
61
  );
62
62
 
63
- const screenLayoutValue = React.useMemo(
63
+ const screeenLayoutValue = React.useMemo(
64
64
  () => ({
65
65
  ...screenLayout,
66
66
  setScreenLayout,
@@ -69,7 +69,7 @@ export function ScreenLayoutContextProvider(props: {
69
69
  );
70
70
 
71
71
  return (
72
- <ScreenLayoutContext.Provider value={screenLayoutValue}>
72
+ <ScreenLayoutContext.Provider value={screeenLayoutValue}>
73
73
  {props.children}
74
74
  </ScreenLayoutContext.Provider>
75
75
  );
@@ -1,15 +1,11 @@
1
1
  import * as React from "react";
2
2
  import * as R from "ramda";
3
- import { View, StyleSheet } from "react-native";
4
3
  import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
5
4
  import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
6
5
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
7
- import { useNavbarState } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
8
- import { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
9
6
 
10
7
  import {
11
8
  getNavigationProps,
12
- getPathAttributes,
13
9
  resolveNavigationPlugin,
14
10
  routeIsPlayerScreen,
15
11
  } from "@applicaster/zapp-react-native-utils/navigationUtils";
@@ -21,25 +17,9 @@ import { ScreenContainer } from "./ScreenContainer";
21
17
  import { TransitionOverlay } from "./TransitionOverlay";
22
18
 
23
19
  import { ScreenLayoutContextProvider } from "./ScreenLayoutContextProvider";
20
+ import { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
24
21
  import { PathnameContext } from "../../../Contexts/PathnameContext";
25
-
26
- import { FocusableGroup } from "../../FocusableGroup";
27
- import { Screen } from "../../Screen/TV/index.web";
28
-
29
- export const ScreenDataContext = React.createContext(null);
30
-
31
- const styles = StyleSheet.create({
32
- container: {
33
- position: "absolute",
34
- width: 1920,
35
- height: 1080,
36
- backgroundColor: "black",
37
- // opacity: 0.5,
38
-
39
- borderWidth: 4,
40
- borderColor: "magenta",
41
- },
42
- });
22
+ import { useNavbarState } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
43
23
 
44
24
  type Components = {
45
25
  NavBar: React.ComponentType<any>;
@@ -47,73 +27,16 @@ type Components = {
47
27
  };
48
28
 
49
29
  type ComponentsExtraProps = {
50
- NavBar?: Record<string, any>;
51
30
  Background?: Record<string, any>;
31
+ NavBar?: Record<string, any>;
52
32
  };
53
33
 
54
34
  type Props = {
55
35
  Components: Components;
56
- componentsExtraProps?: ComponentsExtraProps;
57
- };
58
-
59
- const StackNavigator = ({ Components }: Props) => {
60
- // @ts-ignore
61
- const { mainStack, currentRoute } = useNavigation();
62
-
63
- // console.log("debug_2", "DEBUG", { mainStack });
64
-
65
- const renderScreen = ({ route, action, key, state }, index) => {
66
- const pathAttributes = getPathAttributes({ pathname: route });
67
-
68
- if (R.isEmpty(pathAttributes)) {
69
- return null;
70
- }
71
-
72
- // console.log("debug_2", "StackNavigator - renderScreen", {
73
- // index,
74
- // route,
75
- // state,
76
- // key,
77
- // action,
78
- // });
79
-
80
- const id = `${route}`;
81
-
82
- return (
83
- <FocusableGroup
84
- id={id}
85
- preferredFocus={route === currentRoute}
86
- excludeFromFocusSearching
87
- key={id}
88
- >
89
- <View style={styles.container}>
90
- <ScreenDataContext.Provider value={state}>
91
- <PathnameContext.Provider value={route}>
92
- <Screen route={route} Components={Components} />
93
- </PathnameContext.Provider>
94
- </ScreenDataContext.Provider>
95
- </View>
96
- </FocusableGroup>
97
- );
98
- };
99
-
100
- return mainStack.map(renderScreen);
101
- };
102
-
103
- const TransitionerTV = ({ componentsExtraProps, Components }: Props) => {
104
- return (
105
- <>
106
- <ScreenLayoutContextProvider>
107
- <StackNavigator
108
- componentsExtraProps={componentsExtraProps}
109
- Components={Components}
110
- />
111
- </ScreenLayoutContextProvider>
112
- </>
113
- );
36
+ ComponentsExtraProps?: ComponentsExtraProps;
37
+ children: React.ReactNode;
114
38
  };
115
39
 
116
- // @ts-ignore
117
40
  const Layout = ({ Components, ComponentsExtraProps, children }: Props) => {
118
41
  const navigator = useNavigation();
119
42
  const theme = useTheme();
@@ -175,54 +98,32 @@ const Layout = ({ Components, ComponentsExtraProps, children }: Props) => {
175
98
  }
176
99
 
177
100
  return (
178
- // @ts-ignore
179
- <TransitionerTV
180
- componentsExtraProps={ComponentsExtraProps}
181
- Components={Components}
182
- >
183
- <LayoutContainer>
184
- <ScreenLayoutContextProvider>
185
- <Components.Background
186
- backgroundColor={backgroundColor}
187
- backgroundImageUrl={backgroundImageUrl}
188
- {...R.omit(["ref"])(ComponentsExtraProps?.Background)}
189
- >
190
- {!isTVOS ? navBarContainer : null}
191
- {navBarReady ? (
192
- /**
101
+ <LayoutContainer>
102
+ <ScreenLayoutContextProvider>
103
+ <Components.Background
104
+ backgroundColor={backgroundColor}
105
+ backgroundImageUrl={backgroundImageUrl}
106
+ {...R.omit(["ref"])(ComponentsExtraProps?.Background)}
107
+ >
108
+ {!isTVOS ? navBarContainer : null}
109
+ {navBarReady ? (
110
+ /**
193
111
  Need to re-mount this component on each navigation event
194
112
  It's a hack to work around the fact that we don't support
195
113
  correct screen rendering in our navigator (shared currentRoute state)
196
114
  **/
197
- <PathnameContext.Provider value={navigator.currentRoute}>
198
- <ScreenContainer>{children}</ScreenContainer>
199
- </PathnameContext.Provider>
200
- ) : null}
201
- {/** Render NavBarContainer over ScreenContainer to let tvOS native
115
+ <PathnameContext.Provider value={navigator.currentRoute}>
116
+ <ScreenContainer>{children}</ScreenContainer>
117
+ </PathnameContext.Provider>
118
+ ) : null}
119
+ {/** Render NavBarContainer over ScreenContainer to let tvOS native
202
120
  focus manager works correctly to focus nav bar items first **/}
203
- {isTVOS ? navBarContainer : null}
204
- <TransitionOverlay />
205
- </Components.Background>
206
- </ScreenLayoutContextProvider>
207
- </LayoutContainer>
208
- </TransitionerTV>
121
+ {isTVOS ? navBarContainer : null}
122
+ <TransitionOverlay />
123
+ </Components.Background>
124
+ </ScreenLayoutContextProvider>
125
+ </LayoutContainer>
209
126
  );
210
127
  };
211
128
 
212
129
  export default Layout;
213
-
214
- /// Transitioner:
215
- /*
216
- Component: Transitioner
217
- Responsibilities:
218
- - Transitione between screens.
219
- Tehere is no transition on Tv so no need for transitioner.
220
-
221
- Component: StackNavigator
222
- Responsibilities:
223
- - Render the screens in the stack.
224
- - Render the NavBar? I guess navbar should be part of the screen
225
- - Render the screen.
226
-
227
- Q
228
- */
@@ -25,6 +25,7 @@ type Props = {
25
25
  testID?: string;
26
26
  accessibilityLabel?: string;
27
27
  cellUUID?: string;
28
+ extraProps?: Record<string, any>;
28
29
  };
29
30
 
30
31
  function isStringAsset(asset) {
@@ -108,6 +109,7 @@ export function ActionButton(props: Props) {
108
109
  flavour={flavour}
109
110
  asset={getAssetValue(asset, flavour)}
110
111
  cellUUID={cellUUID}
112
+ {...(props?.extraProps ?? {})}
111
113
  />
112
114
  )}
113
115
  </TouchableOpacity>
@@ -2,6 +2,7 @@ import React from "react";
2
2
  import { isVideoPreviewEnabled } from "@applicaster/zapp-react-native-ui-components/Components/MasterCell/utils";
3
3
  import { LiveImage } from "../LiveImage";
4
4
  import PureImage from "../Image";
5
+ import { useIsScreenActive } from "@applicaster/zapp-react-native-utils/reactHooks";
5
6
 
6
7
  type Props = {
7
8
  enable_video_preview: boolean;
@@ -9,7 +10,10 @@ type Props = {
9
10
  };
10
11
 
11
12
  export const ImageContainer = (props: Props) => {
12
- const Component = isVideoPreviewEnabled(props) ? LiveImage : PureImage;
13
+ const isActive = useIsScreenActive();
14
+
15
+ const Component =
16
+ isVideoPreviewEnabled(props) && isActive ? LiveImage : PureImage;
13
17
 
14
18
  return <Component {...props} />;
15
19
  };
@@ -1,7 +1,6 @@
1
1
  import * as React from "react";
2
2
  import { Text } from "react-native";
3
3
  import { omit } from "ramda";
4
- import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
5
4
  import {
6
5
  textTransform,
7
6
  dateFormat,
@@ -9,6 +8,9 @@ import {
9
8
 
10
9
  import { useTextLabel, withFocusedStyles } from "./hooks";
11
10
  import { withScaledLineHeight } from "./utils";
11
+ import { toNumber } from "@applicaster/zapp-react-native-utils/numberUtils";
12
+ import { MeasurementPortalContext } from "../../../MeasurmentsPortal";
13
+ import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
12
14
 
13
15
  type Props = {
14
16
  style: any;
@@ -32,14 +34,24 @@ const _Text = ({
32
34
  ...otherProps
33
35
  }: Props) => {
34
36
  const _label = useTextLabel({ label, entry });
37
+ const isMeasurement = React.useContext(MeasurementPortalContext);
35
38
 
36
39
  if (isNilOrEmpty(_label)) {
37
40
  return null;
38
41
  }
39
42
 
43
+ // set maximum possible height for the text in case of measurement
44
+ const height =
45
+ isMeasurement && otherProps.numberOfLines && style.lineHeight
46
+ ? toNumber(otherProps.numberOfLines) * toNumber(style.lineHeight)
47
+ : undefined;
48
+
40
49
  return (
41
50
  <Text
42
- style={withScaledLineHeight(withFocusedStyles({ style, otherProps }))}
51
+ style={[
52
+ withScaledLineHeight(withFocusedStyles({ style, otherProps })),
53
+ { height },
54
+ ]}
43
55
  {...withoutLabel(otherProps)}
44
56
  >
45
57
  {dateTransformEnabled
@@ -1,11 +1,23 @@
1
1
  import React from "react";
2
- import { Text } from "react-native";
2
+ import RN from "react-native";
3
3
  import { Provider } from "react-redux";
4
4
  import TestRenderer from "react-test-renderer";
5
- import configureStore from "redux-mock-store";
6
- import CustomText from "../Text";
5
+ import configureStoreFn from "redux-mock-store";
7
6
 
8
- const mockStore = configureStore();
7
+ const mockUseIsRTL = jest.fn(() => true);
8
+ const mockGetIsRTL = jest.fn(() => true);
9
+ const mockUseLocalizedStrings = jest.fn(() => ({}));
10
+ const mockGetUILanguage = jest.fn(() => "he-HE");
11
+
12
+ jest.mock("@applicaster/zapp-react-native-utils/localizationUtils", () => ({
13
+ useIsRTL: mockUseIsRTL,
14
+ getIsRTL: mockGetIsRTL,
15
+ useLocalizedStrings: mockUseLocalizedStrings,
16
+ getUILanguage: mockGetUILanguage,
17
+ }));
18
+
19
+ const CustomText = require("../Text").default;
20
+ const mockStore = configureStoreFn();
9
21
 
10
22
  const defaultProps = {
11
23
  entry: {},
@@ -22,7 +34,7 @@ const getRenderedText = (label: string, store: any) => {
22
34
  </Provider>
23
35
  );
24
36
 
25
- return testRenderer.root.findByType(Text).props.children;
37
+ return testRenderer.root.findByType(RN.Text).props.children;
26
38
  };
27
39
 
28
40
  describe("RTL app: Hebrew text contains english word", () => {
@@ -34,7 +46,7 @@ describe("RTL app: Hebrew text contains english word", () => {
34
46
  remoteConfigurations: { localizations: { he: {} } },
35
47
  appData: {
36
48
  languageCode: "he",
37
- countryCode: "HE",
49
+ countryCode: "IL",
38
50
  },
39
51
  });
40
52
 
@@ -47,18 +59,22 @@ describe("RTL app: Hebrew text contains english word", () => {
47
59
 
48
60
  it("Hebrew text contains not first english word", () => {
49
61
  const renderedText = getRenderedText(textWithNotFirstEnglishWord, store);
50
-
51
62
  expect(renderedText).toEqual(textWithNotFirstEnglishWord);
52
63
  });
53
64
 
54
65
  it("Hebrew text doesn't contain english word", () => {
55
66
  const renderedText = getRenderedText(textWithoutEnglishWord, store);
56
-
57
67
  expect(renderedText).toEqual(textWithoutEnglishWord);
58
68
  });
59
69
  });
60
70
 
61
71
  describe("LTR app: English text", () => {
72
+ beforeAll(() => {
73
+ mockUseIsRTL.mockReturnValue(false);
74
+ mockGetIsRTL.mockReturnValue(false);
75
+ mockGetUILanguage.mockReturnValue("en-US");
76
+ });
77
+
62
78
  const englishText = "Test sentence";
63
79
 
64
80
  const store = mockStore({
@@ -71,7 +87,6 @@ describe("LTR app: English text", () => {
71
87
 
72
88
  it("English text", () => {
73
89
  const renderedText = getRenderedText(englishText, store);
74
-
75
90
  expect(renderedText).toEqual(englishText);
76
91
  });
77
92
  });
@@ -51,9 +51,7 @@ export const CollapsibleTextContainer = ({
51
51
  func: (data) => {
52
52
  return getColorFromData({
53
53
  data,
54
- getValue,
55
- key: "background_color",
56
- defaultColor: additionalStyles.backgroundColor,
54
+ valueFromLayout: additionalStyles.backgroundColor,
57
55
  });
58
56
  },
59
57
  propName: "backgroundColor",
@@ -53,7 +53,7 @@ export const useAsyncRendering = (viewRef: React.RefObject<View>): Return => {
53
53
  measure(viewRef?.current, ({ width, height }) => {
54
54
  setDimensions({
55
55
  width,
56
- height,
56
+ height: Math.ceil(height),
57
57
  });
58
58
  });
59
59
  })
@@ -1,5 +1,6 @@
1
1
  import React from "react";
2
2
  import { View, StyleSheet } from "react-native";
3
+ import { MeasurementPortalContext } from "./MeasurementsPortal";
3
4
 
4
5
  type Props = {
5
6
  Component: React.ElementType;
@@ -19,13 +20,16 @@ const _MeasurementsPortal = (
19
20
  { Component, onLayout, componentProps, containerStyle }: Props,
20
21
  ref
21
22
  ) => (
22
- <View
23
- onLayout={onLayout}
24
- style={[styles.container, containerStyle]}
25
- ref={ref}
26
- >
27
- <Component {...componentProps} />
28
- </View>
23
+ // hack for cell to determine if cell is in measurement mode
24
+ <MeasurementPortalContext.Provider value={{}}>
25
+ <View
26
+ onLayout={onLayout}
27
+ style={[styles.container, containerStyle]}
28
+ ref={ref}
29
+ >
30
+ <Component {...componentProps} />
31
+ </View>
32
+ </MeasurementPortalContext.Provider>
29
33
  );
30
34
 
31
35
  export const MeasurementsPortal = React.forwardRef(_MeasurementsPortal);
@@ -35,7 +35,16 @@ const MeasurementsPortal = ({ Component, onLayout, componentProps }: Props) => {
35
35
  );
36
36
  };
37
37
 
38
- const MeasurementPortalContext = React.createContext(null);
38
+ type MeasurementPortalContextType = {
39
+ measureComponent?: (
40
+ comp: React.ElementType,
41
+ props: Record<any, any>
42
+ ) => Promise<any>;
43
+ measuringInProgress?: boolean;
44
+ };
45
+
46
+ const MeasurementPortalContext =
47
+ React.createContext<null | MeasurementPortalContextType>(null);
39
48
 
40
49
  const MeasurementsPortalContextProvider = ({ children }) => {
41
50
  const Component = React.useRef(View);
@@ -68,7 +68,6 @@ type Props = {
68
68
  mode?: VideoModalMode;
69
69
  loadPipesData: (url: string, options: {}) => void;
70
70
  disableCastAction?: boolean;
71
- groupId?: string;
72
71
  };
73
72
 
74
73
  type ScreenStyles = ZappRiver["styles"] & {
@@ -201,7 +200,6 @@ const PlayerContainerComponent = (props: Props) => {
201
200
  style: videoStyle,
202
201
  mode = VideoModalMode.MAXIMIZED,
203
202
  disableCastAction = false,
204
- groupId,
205
203
  } = props;
206
204
 
207
205
  // Hooks
@@ -251,6 +249,7 @@ const PlayerContainerComponent = (props: Props) => {
251
249
  return;
252
250
  }
253
251
 
252
+ navigator.goBack();
254
253
  showNavBar(true);
255
254
  }, [isModal, navigator.goBack, showNavBar]);
256
255
 
@@ -630,7 +629,6 @@ const PlayerContainerComponent = (props: Props) => {
630
629
  style={styles.focusableGroup}
631
630
  preferredFocus
632
631
  shouldUsePreferredFocus
633
- groupId={groupId}
634
632
  >
635
633
  {/* Video player and components */}
636
634
  <View
@@ -673,6 +671,7 @@ const PlayerContainerComponent = (props: Props) => {
673
671
  setNextVideoPreloadThresholdPercentage={
674
672
  setNextVideoPreloadThresholdPercentage
675
673
  }
674
+ startComponentsAnimation={startComponentsAnimation}
676
675
  >
677
676
  {renderApplePlayer(applePlayerProps)}
678
677
  </Player>
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
  import * as R from "ramda";
3
- import { View, StyleSheet, Platform, Animated, FlatList } from "react-native";
3
+ import { View, StyleSheet, Platform, FlatList } from "react-native";
4
4
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
5
5
  import { RiverItem } from "../RiverItem";
6
6
  import { RiverFooter } from "../RiverFooter";
@@ -217,7 +217,7 @@ function ComponentsMapComponent(props: Props) {
217
217
 
218
218
  const { height: headerHeight, scrollState } = navBarStore.getState();
219
219
 
220
- const offsetY = nativeEvent.contentOffset.y;
220
+ const offsetY = Math.max(0, nativeEvent.contentOffset.y);
221
221
 
222
222
  if (
223
223
  !(scrollState === 0 || scrollState === -headerHeight) &&
@@ -240,7 +240,7 @@ function ComponentsMapComponent(props: Props) {
240
240
  const _onScrollEndDrag = React.useCallback(({ nativeEvent }) => {
241
241
  const { height: headerHeight, scrollState } = navBarStore.getState();
242
242
 
243
- const offsetY = nativeEvent.contentOffset.y;
243
+ const offsetY = Math.max(0, nativeEvent.contentOffset.y);
244
244
 
245
245
  if (
246
246
  !(scrollState === 0 || scrollState === -headerHeight) &&
@@ -260,6 +260,20 @@ function ComponentsMapComponent(props: Props) {
260
260
  }
261
261
  }, []);
262
262
 
263
+ const onScroll = React.useCallback((event) => {
264
+ const {
265
+ nativeEvent: {
266
+ contentOffset: { y },
267
+ layoutMeasurement: { height },
268
+ contentSize: { height: contentHeight },
269
+ },
270
+ } = event;
271
+
272
+ if (y >= 0 && y + height <= contentHeight) {
273
+ scrollY.setValue(y);
274
+ }
275
+ }, []);
276
+
263
277
  if (shouldShowLoadingError) {
264
278
  return <RiverError />;
265
279
  }
@@ -298,20 +312,7 @@ function ComponentsMapComponent(props: Props) {
298
312
  ListFooterComponent={renderFooter}
299
313
  refreshControl={refreshControl}
300
314
  onScrollBeginDrag={onScrollBeginDrag}
301
- onScroll={Animated.event(
302
- [
303
- {
304
- nativeEvent: {
305
- contentOffset: {
306
- y: scrollY,
307
- },
308
- },
309
- },
310
- ],
311
- {
312
- useNativeDriver: false,
313
- }
314
- )}
315
+ onScroll={onScroll}
315
316
  onMomentumScrollEnd={_onMomentumScrollEnd}
316
317
  onScrollEndDrag={_onScrollEndDrag}
317
318
  scrollEventThrottle={16}
@@ -1,5 +1,9 @@
1
1
  import * as React from "react";
2
- import { RefreshControl as RNRefreshControl } from "react-native";
2
+ import {
3
+ Platform,
4
+ RefreshControl as RNRefreshControl,
5
+ StyleSheet,
6
+ } from "react-native";
3
7
  import * as R from "ramda";
4
8
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
5
9
  import { useLocalizedStrings } from "@applicaster/zapp-react-native-utils/localizationUtils";
@@ -8,10 +12,30 @@ import { useSendAnalyticsEventWithFunction } from "@applicaster/zapp-react-nativ
8
12
  import { useCurrentScreenData } from "@applicaster/zapp-react-native-utils/reactHooks/screen";
9
13
  import { loadPipesData } from "@applicaster/zapp-react-native-redux/ZappPipes";
10
14
  import { useDispatch } from "react-redux";
15
+ import { useShallow } from "zustand/react/shallow";
16
+ import { useScreenContextV2 } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
17
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
11
18
 
12
19
  const BRIGHTNESS_THRESHOLD = 160;
13
20
  const ABOVE_DEFAULT_COLOR = "gray";
14
21
  const BELOW_DEFAULT_COLOR = "white";
22
+ const BASE_INDICATOR_HEIGHT = 40;
23
+ const BASE_INDICATOR_OFFSET = 16;
24
+
25
+ /** Return spacing required to position indicator below the navbar bottom line (Android only) */
26
+ const getRefreshControlOffset = () => {
27
+ return Platform.OS === "ios"
28
+ ? 0
29
+ : BASE_INDICATOR_HEIGHT + BASE_INDICATOR_OFFSET;
30
+ };
31
+
32
+ const styles = StyleSheet.create({
33
+ // eslint-disable-next-line react-native/no-color-literals
34
+ control: {
35
+ zIndex: 1,
36
+ backgroundColor: "transparent",
37
+ },
38
+ });
15
39
 
16
40
  const getBrightness = (RGBcolor) => {
17
41
  const RGBvalues = RGBcolor.substring(
@@ -91,6 +115,34 @@ export const usePullToRefresh = (
91
115
  };
92
116
  };
93
117
 
118
+ /** Returns the offset for the progress view of the RefreshControl component
119
+ * based on navbar content position */
120
+ export const useGetProgressViewOffset = () => {
121
+ const { _navBarStore } = useScreenContextV2();
122
+ const { top } = useSafeAreaInsets();
123
+
124
+ const offset = getRefreshControlOffset();
125
+
126
+ const { height, contentPosition } = _navBarStore(
127
+ useShallow(({ contentPosition, height }) => ({ contentPosition, height }))
128
+ );
129
+
130
+ if (height === 0) {
131
+ return offset;
132
+ }
133
+
134
+ switch (contentPosition) {
135
+ case "on_bottom_status_bar":
136
+ // navbar height
137
+ return offset + height - top;
138
+ case "on_top_screen":
139
+ // navbar height + status bar height
140
+ return offset + height;
141
+ default:
142
+ return offset;
143
+ }
144
+ };
145
+
94
146
  export function RefreshControl(props: {
95
147
  pullToRefreshPipesV1RefreshingStateUpdater?: (refreshing: boolean) => void;
96
148
  refreshingPipesV1?: boolean;
@@ -211,6 +263,7 @@ export function RefreshControl(props: {
211
263
  refreshing={refreshing}
212
264
  onRefresh={onRefreshHandler}
213
265
  colors={[preparedIndicatorColor]}
266
+ progressViewOffset={useGetProgressViewOffset()}
214
267
  progressBackgroundColor={preparedIndicatorBackgroundColor}
215
268
  // @ts-ignore in RN68 this `size` became string
216
269
  size={indicatorSize}
@@ -222,6 +275,7 @@ export function RefreshControl(props: {
222
275
  : null
223
276
  }
224
277
  titleColor={preparedTitleUnderIndicatorColor}
278
+ style={styles.control}
225
279
  />
226
280
  );
227
281
  }
@@ -112,6 +112,7 @@ export const River = (props: Props) => {
112
112
 
113
113
  focusManager.setupInitialFocus(groupId, true);
114
114
  focusManager.setInitialFocus();
115
+ focusManager.enableAutomaticFocus();
115
116
  }
116
117
  }, [river, componentsMapExtraProps]);
117
118
 
@@ -32,13 +32,12 @@ type Props = {
32
32
  nextFocusLeft?: React.Ref<any>;
33
33
  nextFocusUp?: React.Ref<any>;
34
34
  };
35
- groupId?: string;
36
35
  };
37
36
 
38
37
  export function ScreenResolverComponent(props: Props) {
39
38
  useScreenAnalytics(props);
40
39
 
41
- const { screenType, screenId, screenData, groupId } = props;
40
+ const { screenType, screenId, screenData } = props;
42
41
 
43
42
  const { hookPlugin } = screenData || {};
44
43
 
@@ -82,7 +81,6 @@ export function ScreenResolverComponent(props: Props) {
82
81
  item={screenData}
83
82
  mode={mode === "PIP" ? "PIP" : "FULLSCREEN"}
84
83
  isModal={false}
85
- groupId={groupId}
86
84
  />
87
85
  );
88
86
  }
@@ -1,7 +1,10 @@
1
1
  import React, { useContext } from "react";
2
2
  import { Animated } from "react-native";
3
3
 
4
- import { useSafeAreaFrame } from "react-native-safe-area-context";
4
+ import {
5
+ useSafeAreaFrame,
6
+ useSafeAreaInsets,
7
+ } from "react-native-safe-area-context";
5
8
  import { useGetBottomTabBarHeight } from "@applicaster/zapp-react-native-utils/reactHooks";
6
9
 
7
10
  import { useConfiguration } from "../utils";
@@ -91,8 +94,16 @@ const Provider = ({ children }: { children: React.ReactNode }) => {
91
94
  const dragVideoPlayerY = React.useRef(new Animated.Value(0)).current;
92
95
 
93
96
  const { height } = useSafeAreaFrame();
97
+ const { bottom: bottomSafeArea } = useSafeAreaInsets();
94
98
  const bottomTabBarHeight = useGetBottomTabBarHeight();
95
- const minValue = height - minimisedHeight - bottomTabBarHeight;
99
+
100
+ /*
101
+ If bottomTabBarHeight is equal 0 it means an app does not use bottomTabBar.
102
+ Because of this we need to minus bottom SafeArea offset.
103
+ */
104
+ const minValue =
105
+ height - minimisedHeight - (bottomTabBarHeight || bottomSafeArea);
106
+
96
107
  const modalSnapPoints = React.useMemo(() => [0, minValue], [minValue]);
97
108
  // Last snap state which will helps us to make smooth responder to scrollview animation
98
109
  const [lastSnap, setLastSnap] = React.useState<number>(modalSnapPoints[0]);
@@ -129,17 +129,17 @@ const VideoModalComponent = () => {
129
129
  const pathname = `${ROUTE_TYPES.VIDEO_MODAL}/${item?.id}`;
130
130
 
131
131
  return (
132
- <AnimatedPlayerModalWrapper
133
- style={[
134
- styles.container,
135
- {
136
- backgroundColor,
137
- },
138
- ]}
139
- >
140
- <ScreenContextProvider pathname={pathname}>
141
- <PathnameContext.Provider value={pathname}>
142
- {itemIdHooksFinished === item?.id ? (
132
+ <ScreenContextProvider pathname={pathname}>
133
+ <PathnameContext.Provider value={pathname}>
134
+ {itemIdHooksFinished === item?.id ? (
135
+ <AnimatedPlayerModalWrapper
136
+ style={[
137
+ styles.container,
138
+ {
139
+ backgroundColor,
140
+ },
141
+ ]}
142
+ >
143
143
  <HandlePlayable
144
144
  item={item}
145
145
  isModal={mode !== "PIP"}
@@ -147,14 +147,14 @@ const VideoModalComponent = () => {
147
147
  modalWidth={modalSize.width}
148
148
  mode={mode}
149
149
  />
150
- ) : (
151
- <View style={styles.loaderContainer}>
152
- <Spinner />
153
- </View>
154
- )}
155
- </PathnameContext.Provider>
156
- </ScreenContextProvider>
157
- </AnimatedPlayerModalWrapper>
150
+ </AnimatedPlayerModalWrapper>
151
+ ) : (
152
+ <View style={styles.loaderContainer}>
153
+ <Spinner />
154
+ </View>
155
+ )}
156
+ </PathnameContext.Provider>
157
+ </ScreenContextProvider>
158
158
  );
159
159
  };
160
160
 
@@ -26,7 +26,7 @@ interface NavBarStoreState {
26
26
  setSummary: (subtitle: string) => void;
27
27
  height: number;
28
28
  scrollState: number;
29
- isOnBottomNavBar: boolean;
29
+ contentPosition: string;
30
30
  scrollYAnimated: Animated.Value;
31
31
  }
32
32
 
@@ -52,7 +52,7 @@ const createStore = () =>
52
52
  visible: true,
53
53
  height: 0,
54
54
  scrollState: 0,
55
- isOnBottomNavBar: true,
55
+ contentPosition: "",
56
56
  scrollYAnimated: new Animated.Value(0),
57
57
  setTitle(title) {
58
58
  set({ title });
@@ -63,7 +63,7 @@ function prepareContext(
63
63
  const parent = findParent(currentContext, route, getChildren);
64
64
 
65
65
  const newEntry = {
66
- route,
66
+ route: route,
67
67
  id: uuid(),
68
68
  data: newContext,
69
69
  } as SingleEntry;
package/events/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export enum QBUIComponentEvents {
2
2
  toggleGradient = "toggleGradient",
3
3
  topMenuBarTV_onFocus = "topMenuBarTV_onFocus",
4
+ navigatorReplaceItem = "navigatorReplaceItem",
4
5
  }
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.5208030571",
3
+ "version": "12.0.0-alpha.5227180498",
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.5208030571",
38
- "@applicaster/zapp-react-native-bridge": "12.0.0-alpha.5208030571",
39
- "@applicaster/zapp-react-native-redux": "12.0.0-alpha.5208030571",
40
- "@applicaster/zapp-react-native-utils": "12.0.0-alpha.5208030571",
37
+ "@applicaster/applicaster-types": "12.0.0-alpha.5227180498",
38
+ "@applicaster/zapp-react-native-bridge": "12.0.0-alpha.5227180498",
39
+ "@applicaster/zapp-react-native-redux": "12.0.0-alpha.5227180498",
40
+ "@applicaster/zapp-react-native-utils": "12.0.0-alpha.5227180498",
41
41
  "promise": "^8.3.0",
42
42
  "react-router-native": "^5.1.2",
43
43
  "url": "^0.11.0",
@@ -1,151 +0,0 @@
1
- import * as React from "react";
2
- import { last, pathOr } from "ramda";
3
- import { ScreenResolver } from "../../ScreenResolver";
4
- import {
5
- getNavigationProps,
6
- getPathAttributes,
7
- resolveNavigationPlugin,
8
- } from "@applicaster/zapp-react-native-utils/navigationUtils";
9
- import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks";
10
- import { NavBarContainer } from "../../Layout/TV/NavBarContainer";
11
- import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
12
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
13
-
14
- type Components = {
15
- NavBar: React.ComponentType<any>;
16
- Background: React.ComponentType<any>;
17
- };
18
-
19
- type Props = {
20
- route: string;
21
- Components: Components;
22
- };
23
-
24
- const isPlayer = (routeState): boolean => {
25
- const pluginType = pathOr(
26
- "unknown_plugin_type",
27
- ["state", "screen", "plugin_type"],
28
- routeState
29
- );
30
-
31
- return pluginType === "player";
32
- };
33
-
34
- const getActiveRiver = ({
35
- screenId,
36
- screenType,
37
- routeState,
38
- rivers,
39
- fallbackActiveRiver,
40
- }) => {
41
- if (rivers[screenId]) {
42
- // river presents
43
- return screenType === "hooks"
44
- ? routeState?.state?.entry?.payload
45
- : routeState?.state?.screen;
46
- }
47
-
48
- // screenId is not presented into rivers, use fallback instead
49
- return fallbackActiveRiver;
50
- };
51
-
52
- const getNavigations = ({
53
- screenId,
54
- screenType,
55
- routeState,
56
- rivers,
57
- fallbackNavigations,
58
- }) => {
59
- if (rivers[screenId]) {
60
- // river presents
61
- return screenType === "hooks"
62
- ? routeState?.state?.entry?.payload.navigations
63
- : routeState?.state?.screen?.navigations;
64
- }
65
-
66
- // screenId is not presented into rivers, use fallback instead
67
- return fallbackNavigations;
68
- };
69
-
70
- export const Screen = (props: Props) => {
71
- if (!props.route) {
72
- throw new Error("Required props route is missing");
73
- }
74
-
75
- const navigator = useNavigation();
76
- const { plugins = [], rivers = [] } = usePickFromState(["plugins", "rivers"]);
77
-
78
- const pathAttributes = getPathAttributes({ pathname: props.route });
79
- const routeState = navigator.getStackForPathname(props.route);
80
-
81
- const { screenType, screenId } = last(pathAttributes);
82
-
83
- // console.log("debug_2", "Screen", {
84
- // screenType,
85
- // routeState,
86
- // route: props.route,
87
- // navigator,
88
- // });
89
-
90
- const activeRiver = getActiveRiver({
91
- screenId,
92
- screenType,
93
- routeState,
94
- rivers,
95
- fallbackActiveRiver: navigator.activeRiver,
96
- });
97
-
98
- const navigationProps = getNavigationProps({
99
- navigator: {
100
- // @ts-ignore
101
- activeRiver,
102
- // @ts-ignore
103
- screenTitle: routeState.state.entry?.title || routeState.screen?.name,
104
- },
105
- category: "menu",
106
- });
107
-
108
- const navigations = getNavigations({
109
- screenId,
110
- screenType,
111
- routeState,
112
- rivers,
113
- fallbackNavigations: navigator.activeRiver.navigations,
114
- });
115
-
116
- const menuPlugin = resolveNavigationPlugin({
117
- category: "menu",
118
- navigations,
119
- plugins,
120
- }) as QuickBrickPlugin;
121
-
122
- const NavBar = (props.Components.NavBar ||
123
- menuPlugin.module) as React.ComponentType<any>;
124
-
125
- const isNavBarVisible = !isPlayer(routeState);
126
-
127
- const screenData =
128
- screenType === "hooks"
129
- ? routeState?.state?.entry
130
- : routeState?.state?.screen;
131
-
132
- return (
133
- <>
134
- <NavBarContainer isVisible={isNavBarVisible} onReady={noop}>
135
- <NavBar
136
- groupId={props.route}
137
- isVisible={isNavBarVisible}
138
- menuPlugin={menuPlugin}
139
- navigationProps={navigationProps}
140
- // Maybe activeRiver?
141
- />
142
- </NavBarContainer>
143
- <ScreenResolver
144
- screenType={screenType}
145
- screenId={screenId}
146
- screenData={screenData}
147
- groupId={props.route}
148
- />
149
- </>
150
- );
151
- };