@applicaster/zapp-react-native-ui-components 13.0.5-rc.0 → 14.0.0-alpha.1101330035

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 (32) hide show
  1. package/Components/AudioPlayer/tv/Layout.tsx +4 -2
  2. package/Components/Cell/index.js +6 -2
  3. package/Components/Focusable/Focusable.tsx +5 -3
  4. package/Components/Focusable/__tests__/index.android.test.tsx +3 -0
  5. package/Components/Focusable/index.android.tsx +12 -8
  6. package/Components/GeneralContentScreen/GeneralContentScreen.tsx +0 -2
  7. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -27
  8. package/Components/MasterCell/DefaultComponents/Text/index.tsx +1 -0
  9. package/Components/MasterCell/utils/behaviorProvider.ts +82 -14
  10. package/Components/MasterCell/utils/index.ts +23 -3
  11. package/Components/PlayerContainer/PlayerContainer.tsx +17 -15
  12. package/Components/River/TV/River.tsx +0 -3
  13. package/Components/TopMarginApplicator/TopMarginApplicator.tsx +16 -15
  14. package/Components/Transitioner/Scene.tsx +1 -0
  15. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +9 -1
  16. package/Components/VideoModal/PlayerDetails.tsx +24 -2
  17. package/Components/VideoModal/PlayerWrapper.tsx +26 -142
  18. package/Components/VideoModal/VideoModal.tsx +3 -17
  19. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -7
  20. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +44 -240
  21. package/Components/VideoModal/hooks/index.ts +0 -2
  22. package/Components/VideoModal/hooks/useModalSize.ts +18 -2
  23. package/Components/VideoModal/utils.ts +6 -0
  24. package/Components/Viewport/VisibilitySensor/VisibilitySensor.tsx +3 -3
  25. package/Components/default-cell-renderer/viewTrees/tv/DefaultCell/index.ts +3 -3
  26. package/Decorators/RiverFeedLoader/index.tsx +8 -2
  27. package/Decorators/RiverFeedLoader/utils/index.ts +7 -2
  28. package/Decorators/ZappPipesDataConnector/index.tsx +20 -2
  29. package/index.d.ts +0 -1
  30. package/package.json +5 -9
  31. package/Components/VideoModal/hooks/useBackgroundColor.ts +0 -10
  32. package/Decorators/Navigator/__tests__/react-router-native-mock.js +0 -11
@@ -110,22 +110,24 @@ export function AudioPlayerTVLayout({
110
110
  ...style,
111
111
  },
112
112
  samsung_tv: {
113
+ position: "absolute",
113
114
  margin: "auto",
114
115
  display: "flex",
115
116
  flexWrap: "wrap",
117
+ height: "100vh",
116
118
  width: "100vw",
117
- flex: 1,
118
119
  alignItems: "center",
119
120
  justifyContent: "center",
120
121
  flexDirection: directionStyles(isRTL).flexDirection,
121
122
  backgroundColor: backgroundColorStyle,
122
123
  },
123
124
  lg_tv: {
125
+ position: "absolute",
124
126
  margin: "auto",
125
127
  display: "flex",
126
128
  flexWrap: "wrap",
129
+ height: "100vh",
127
130
  width: "100vw",
128
- flex: 1,
129
131
  alignItems: "center",
130
132
  justifyContent: "center",
131
133
  flexDirection: directionStyles(isRTL).flexDirection,
@@ -3,11 +3,15 @@ import * as R from "ramda";
3
3
  import { connectToStore } from "@applicaster/zapp-react-native-redux";
4
4
  import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
5
5
 
6
- import { HorizontalScrollContext, RiverOffsetContext } from "../../Contexts";
6
+ import {
7
+ HorizontalScrollContext,
8
+ RiverOffsetContext,
9
+ ScreenScrollingContext,
10
+ } from "../../Contexts";
11
+
7
12
  import { CellComponent } from "./Cell";
8
13
  import { TvOSCellComponent } from "./TvOSCellComponent";
9
14
  import { withConsumer } from "../../Contexts/HeaderOffsetContext";
10
- import { ScreenScrollingContext } from "../../Contexts/ScreenScrollingContext";
11
15
 
12
16
  import { ScreenLayoutContextConsumer } from "../../Contexts/ScreenLayoutContext";
13
17
  import { createContext } from "@applicaster/zapp-react-native-utils/reactUtils/createContext";
@@ -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?: React.CSSProperties;
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>
@@ -38,6 +38,9 @@ describe("Focusable", () => {
38
38
  });
39
39
 
40
40
  it("updates disableFocus state when disableFocus prop changes", () => {
41
+ const unregister = jest.fn();
42
+ mockFocusManager.registerFocusable.mockReturnValue(unregister);
43
+
41
44
  const { rerender } = render(
42
45
  <Focusable id="test-id" disableFocus={false}>
43
46
  <Touchable testID="touchable" />
@@ -43,11 +43,13 @@ export const FocusableContext = React.createContext<
43
43
  // eslint-disable-next-line
44
44
  setIsFocusable: (enableFocus: boolean) => void;
45
45
  ref: FocusManager.FocusableRef;
46
+ parentFocusableId: Option<string>;
46
47
  } & ParentFocus
47
48
  >({
48
49
  focused: false,
49
50
  setIsFocusable: () => {},
50
51
  ref: { current: null },
52
+ parentFocusableId: undefined,
51
53
  });
52
54
 
53
55
  export const useFocusable = () => React.useContext(FocusableContext);
@@ -74,7 +76,7 @@ function FocusableComponent(props: Props, forwardedRef) {
74
76
 
75
77
  const isRTL = useIsRTL();
76
78
  const focusManager = useFocusManager();
77
- const { ref: parentFocusable } = useFocusable();
79
+ const { ref: parentFocusableRef, parentFocusableId } = useFocusable();
78
80
  const touchableRef = React.useRef(null);
79
81
 
80
82
  const [focused, setFocused] = React.useState(() =>
@@ -98,13 +100,14 @@ function FocusableComponent(props: Props, forwardedRef) {
98
100
  }
99
101
  }, [disableFocus]);
100
102
 
101
- React.useEffect(() => {
103
+ React.useLayoutEffect(() => {
102
104
  if (id) {
103
- const unregister = focusManager.registerFocusable(
105
+ const unregister = focusManager.registerFocusable({
104
106
  touchableRef,
105
- parentFocusable,
106
- isFocusableCell
107
- );
107
+ parentFocusableRef,
108
+ isFocusableCell,
109
+ parentFocusableId,
110
+ });
108
111
 
109
112
  onRegister();
110
113
 
@@ -112,7 +115,7 @@ function FocusableComponent(props: Props, forwardedRef) {
112
115
  unregister();
113
116
  };
114
117
  }
115
- }, [id, onRegister, isFocusableCell]);
118
+ }, [id, onRegister, isFocusableCell, parentFocusableId]);
116
119
 
117
120
  if (R.isNil(id)) {
118
121
  // eslint-disable-next-line no-console
@@ -164,8 +167,9 @@ function FocusableComponent(props: Props, forwardedRef) {
164
167
  ...parentFocus,
165
168
  ref: touchableRef,
166
169
  setIsFocusable,
170
+ parentFocusableId: id,
167
171
  };
168
- }, [parentFocus, focused]);
172
+ }, [parentFocus, focused, id]);
169
173
 
170
174
  return (
171
175
  <Touchable
@@ -30,7 +30,6 @@ export const GeneralContentScreen = ({
30
30
  isScreenWrappedInContainer,
31
31
  componentsMapExtraProps = {},
32
32
  focused,
33
- extraOffset,
34
33
  parentFocus,
35
34
  containerHeight,
36
35
  preferredFocus = false,
@@ -122,7 +121,6 @@ export const GeneralContentScreen = ({
122
121
  isScreenWrappedInContainer={isScreenWrappedInContainer}
123
122
  parentFocus={parentFocus}
124
123
  focused={focused}
125
- extraOffset={extraOffset}
126
124
  containerHeight={containerHeight}
127
125
  preferredFocus={preferredFocus}
128
126
  {...componentsMapExtraProps}
@@ -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
- wrapperRef?.current?.measure((x, y, width, height, pageX, pageY) => {
71
- const top = pageY;
72
- const bottom = top + height;
73
- const left = pageX;
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) {
@@ -52,6 +52,7 @@ const _Text = ({
52
52
  withScaledLineHeight(withFocusedStyles({ style, otherProps })),
53
53
  { height },
54
54
  ]}
55
+ allowFontScaling={false}
55
56
  {...withoutLabel(otherProps)}
56
57
  >
57
58
  {dateTransformEnabled
@@ -1,28 +1,58 @@
1
1
  import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils";
2
- import { StorageSingleValueProvider } from "@applicaster/zapp-react-native-bridge/ZappStorage/StorageSingleSelectProvider";
2
+ import { StorageSingleValueProvider } from "@applicaster/zapp-react-native-utils/storage/StorageSingleSelectProvider";
3
3
  import { PushTopicManager } from "@applicaster/zapp-react-native-bridge/PushNotifications/PushTopicManager";
4
- import { StorageMultiSelectProvider } from "@applicaster/zapp-react-native-bridge/ZappStorage/StorageMultiSelectProvider";
4
+ import { StorageMultiSelectProvider } from "@applicaster/zapp-react-native-utils/storage/StorageMultiSelectProvider";
5
5
  import React, { useEffect } from "react";
6
6
  import { usePlayer } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer";
7
7
  import { BehaviorSubject } from "rxjs";
8
8
  import { masterCellLogger } from "../logger";
9
9
  import get from "lodash/get";
10
-
11
- const parseContextKey = (key: string): string | null => {
12
- if (!key?.startsWith("@{ctx/")) return null;
13
-
14
- return key.substring("@{ctx/".length, key.length - 1);
10
+ import { ScreenMultiSelectProvider } from "@applicaster/zapp-react-native-utils/storage/ScreenStateMultiSelectProvider";
11
+ import { ScreenSingleValueProvider } from "@applicaster/zapp-react-native-utils/storage/ScreenSingleValueProvider";
12
+ import { useRoute } from "@applicaster/zapp-react-native-utils/reactHooks";
13
+ import { useScreenStateStore } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useScreenStateStore";
14
+
15
+ const parseContextKey = (
16
+ key: string,
17
+ context: string = "ctx"
18
+ ): string | null => {
19
+ if (!key?.startsWith(`@{${context}/`)) return null;
20
+
21
+ return key.substring(`@{${context}/`.length, key.length - 1);
15
22
  };
16
23
 
17
24
  const getDataSourceProvider = (
18
- behavior: Behavior
25
+ behavior: Behavior,
26
+ screenRoute: string,
27
+ screenStateStore: ScreenStateStore
19
28
  ): BehaviorSubject<string[] | string> | null => {
20
29
  if (!behavior) return null;
21
30
 
22
31
  const selection = String(behavior.current_selection);
32
+ const screenKey = parseContextKey(selection, "screen");
33
+
34
+ if (screenKey) {
35
+ if (behavior.select_mode === "multi") {
36
+ return ScreenMultiSelectProvider.getProvider(
37
+ screenKey,
38
+ screenRoute,
39
+ screenStateStore
40
+ ).getObservable();
41
+ }
42
+
43
+ if (behavior.select_mode === "single") {
44
+ return ScreenSingleValueProvider.getProvider(
45
+ screenKey,
46
+ screenRoute,
47
+ screenStateStore
48
+ ).getObservable();
49
+ }
50
+ }
51
+
23
52
  const contextKey = parseContextKey(selection);
24
53
 
25
54
  if (contextKey) {
55
+ // TODO: Add storage scope to behavior
26
56
  if (behavior.select_mode === "multi") {
27
57
  return StorageMultiSelectProvider.getProvider(contextKey).getObservable();
28
58
  }
@@ -41,6 +71,8 @@ const getDataSourceProvider = (
41
71
 
42
72
  export const useBehaviorUpdate = (behavior: Behavior) => {
43
73
  const [lastUpdate, setLastUpdate] = React.useState<number | null>(null);
74
+ const screenRoute = useRoute()?.pathname || "";
75
+ const screenStateStore = useScreenStateStore();
44
76
  const player = usePlayer();
45
77
 
46
78
  const triggerUpdate = () => setLastUpdate(Date.now());
@@ -48,7 +80,11 @@ export const useBehaviorUpdate = (behavior: Behavior) => {
48
80
  useEffect(() => {
49
81
  if (!behavior) return;
50
82
 
51
- const dataSource = getDataSourceProvider(behavior);
83
+ const dataSource = getDataSourceProvider(
84
+ behavior,
85
+ screenRoute,
86
+ screenStateStore
87
+ );
52
88
 
53
89
  if (dataSource) {
54
90
  const subscription = dataSource.subscribe(triggerUpdate);
@@ -72,10 +108,17 @@ export const useBehaviorUpdate = (behavior: Behavior) => {
72
108
 
73
109
  // We cant use async in this function (its inside render),
74
110
  // so we rely on useBehaviorUpdate to update current value and trigger re-render
75
- export const isCellSelected = (
76
- item: ZappEntry,
77
- behavior?: Behavior
78
- ): boolean => {
111
+ export const isCellSelected = ({
112
+ item,
113
+ screenRoute,
114
+ screenStateStore,
115
+ behavior,
116
+ }: {
117
+ item: ZappEntry;
118
+ screenRoute: string;
119
+ screenStateStore: ScreenStateStore;
120
+ behavior?: Behavior;
121
+ }): boolean => {
79
122
  if (!behavior) return false;
80
123
 
81
124
  const id = behavior.selector ? get(item, behavior.selector) : item.id;
@@ -99,7 +142,32 @@ export const isCellSelected = (
99
142
  }
100
143
 
101
144
  const selection = String(behavior.current_selection);
102
- const contextKey = parseContextKey(selection);
145
+
146
+ const screenKey = parseContextKey(selection, "screen");
147
+
148
+ if (screenKey) {
149
+ if (behavior.select_mode === "single") {
150
+ const selectedItem = ScreenSingleValueProvider.getProvider(
151
+ screenKey,
152
+ screenRoute,
153
+ screenStateStore
154
+ ).getValue();
155
+
156
+ return selectedItem === String(id);
157
+ }
158
+
159
+ if (behavior.select_mode === "multi") {
160
+ const selectedItems = ScreenMultiSelectProvider.getProvider(
161
+ screenKey,
162
+ screenRoute,
163
+ screenStateStore
164
+ ).getSelectedItems();
165
+
166
+ return selectedItems?.includes(String(id));
167
+ }
168
+ }
169
+
170
+ const contextKey = parseContextKey(selection, "ctx");
103
171
 
104
172
  if (contextKey) {
105
173
  if (behavior.select_mode === "single") {
@@ -8,6 +8,8 @@ import { masterCellLogger } from "../logger";
8
8
  import { getCellState } from "../../Cell/utils";
9
9
  import { getColorFromData } from "@applicaster/zapp-react-native-utils/cellUtils";
10
10
  import { isCellSelected, useBehaviorUpdate } from "./behaviorProvider";
11
+ import { useRoute } from "@applicaster/zapp-react-native-utils/reactHooks";
12
+ import { useScreenStateStore } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useScreenStateStore";
11
13
 
12
14
  const hasElementSpecificViewType = (viewType) => (element) => {
13
15
  if (R.isNil(element)) {
@@ -190,8 +192,18 @@ export const getFocusedButtonId = (focusable) => {
190
192
  });
191
193
  };
192
194
 
193
- export const isSelected = (item: ZappEntry, behavior?: Behavior) => {
194
- return isCellSelected(item, behavior);
195
+ export const isSelected = ({
196
+ item,
197
+ screenRoute,
198
+ screenStateStore,
199
+ behavior,
200
+ }: {
201
+ item: ZappEntry;
202
+ screenRoute: string;
203
+ screenStateStore: ScreenStateStore;
204
+ behavior?: Behavior;
205
+ }) => {
206
+ return isCellSelected({ item, screenRoute, screenStateStore, behavior });
195
207
  };
196
208
 
197
209
  export const useCellState = ({
@@ -204,9 +216,17 @@ export const useCellState = ({
204
216
  focused: boolean;
205
217
  }): CellState => {
206
218
  const lastUpdate = useBehaviorUpdate(behavior);
219
+ const router = useRoute();
220
+ const screenStateStore = useScreenStateStore();
207
221
 
208
222
  const _isSelected = useMemo(
209
- () => isSelected(item, behavior),
223
+ () =>
224
+ isSelected({
225
+ item,
226
+ screenRoute: router?.pathname,
227
+ screenStateStore,
228
+ behavior,
229
+ }),
210
230
  [behavior, item, lastUpdate]
211
231
  );
212
232
 
@@ -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;
@@ -105,11 +105,6 @@ const isTvOS = isTvOSPlatform();
105
105
  // height for container with additional content below player
106
106
  const INLINE_CONTAINER_CONTENT_HEIGHT = 400;
107
107
 
108
- // Default Y offset for anchor points on non-Android TV platforms
109
- const DEFAULT_OFFSET_Y = -600;
110
-
111
- const EXTRA_ANCHOR_POINT_Y_OFFSET = isAndroidTV ? 0 : DEFAULT_OFFSET_Y;
112
-
113
108
  const withBorderHack = () => {
114
109
  if (isAndroidTV) {
115
110
  /* @HACK: see GH#7269 */
@@ -132,7 +127,7 @@ const webStyles = {
132
127
  playerScreen: {
133
128
  flex: 1,
134
129
  height: "100vh",
135
- background: "black",
130
+ backgroundColor: "black",
136
131
  },
137
132
  playerWrapper: {
138
133
  height: "100%",
@@ -150,7 +145,6 @@ const nativeStyles = {
150
145
  },
151
146
  playerScreen: {
152
147
  flex: 1,
153
- backgroundColor: "black",
154
148
  overflow: "hidden",
155
149
  },
156
150
  playerWrapper: {
@@ -570,8 +564,9 @@ const PlayerContainerComponent = (props: Props) => {
570
564
  const isInlineTV = isInlineTVUtil(screenData);
571
565
 
572
566
  const inline =
573
- [VideoModalMode.MAXIMIZED, VideoModalMode.MINIMIZED].includes(mode) ||
574
- isInlineTV;
567
+ [VideoModalMode.MAXIMIZED, VideoModalMode.MINIMIZED].includes(
568
+ mode as any
569
+ ) || isInlineTV;
575
570
 
576
571
  const value = React.useMemo(
577
572
  () => ({ playerId: state.playerId }),
@@ -592,7 +587,11 @@ const PlayerContainerComponent = (props: Props) => {
592
587
  );
593
588
  }
594
589
 
595
- if (screen_background_color && mode !== VideoModalMode.FULLSCREEN) {
590
+ if (
591
+ screen_background_color &&
592
+ mode !== VideoModalMode.FULLSCREEN &&
593
+ isTV()
594
+ ) {
596
595
  updatedStyles.playerScreen.backgroundColor = screen_background_color;
597
596
  }
598
597
 
@@ -622,6 +621,8 @@ const PlayerContainerComponent = (props: Props) => {
622
621
  playNextData,
623
622
  };
624
623
 
624
+ const pointerEventsProp = mode === "MINIMIZED" ? "box-none" : "auto";
625
+
625
626
  return (
626
627
  <PlayerStateContext.Provider value={value}>
627
628
  <PlayerContainerContextProvider
@@ -642,14 +643,17 @@ const PlayerContainerComponent = (props: Props) => {
642
643
  preferredFocus
643
644
  shouldUsePreferredFocus
644
645
  groupId={groupId}
646
+ pointerEvents={pointerEventsProp}
645
647
  >
646
648
  {/* Video player and components */}
647
649
  <View
648
650
  style={styles.playerScreen}
649
651
  testID={"player-screen-container"}
652
+ pointerEvents={pointerEventsProp}
650
653
  >
651
654
  {/* Player container */}
652
655
  <View
656
+ pointerEvents={pointerEventsProp}
653
657
  style={[
654
658
  styles.playerWrapper,
655
659
  // eslint-disable-next-line react-native/no-inline-styles, react-native/no-color-literals
@@ -721,13 +725,11 @@ const PlayerContainerComponent = (props: Props) => {
721
725
  key={item.id}
722
726
  groupId={FocusableGroupMainContainerId}
723
727
  cellTapAction={onCellTap}
724
- extraAnchorPointYOffset={
725
- EXTRA_ANCHOR_POINT_Y_OFFSET
726
- }
728
+ extraAnchorPointYOffset={0}
727
729
  isScreenWrappedInContainer={true}
728
730
  containerHeight={styles.inlineRiver.height}
729
731
  componentsMapExtraProps={{
730
- isNestedComponentsMap: R.T,
732
+ isNestedComponentsMap: true,
731
733
  }}
732
734
  />
733
735
  )}
@@ -26,7 +26,6 @@ type Props = {
26
26
  componentsMapExtraProps?: any;
27
27
  isInsideContainer?: boolean;
28
28
  extraAnchorPointYOffset: number;
29
- extraOffset: number;
30
29
  river?: ZappRiver | ZappEntry;
31
30
  };
32
31
 
@@ -39,7 +38,6 @@ export const River = (props: Props) => {
39
38
  componentsMapExtraProps,
40
39
  isInsideContainer,
41
40
  extraAnchorPointYOffset,
42
- extraOffset,
43
41
  } = props;
44
42
 
45
43
  const { title: screenTitle, summary: screenSummary } = useNavbarState();
@@ -120,7 +118,6 @@ export const River = (props: Props) => {
120
118
  <GeneralContentScreen
121
119
  feed={feedData}
122
120
  screenId={screenId}
123
- extraOffset={extraOffset}
124
121
  isScreenWrappedInContainer={isInsideContainer}
125
122
  extraAnchorPointYOffset={extraAnchorPointYOffset}
126
123
  componentsMapExtraProps={componentsMapExtraProps}
@@ -1,17 +1,17 @@
1
1
  import React from "react";
2
- import { View, ViewProps, ViewStyle } from "react-native";
2
+ import { View, ViewStyle } from "react-native";
3
3
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
4
4
  import { useCurrentScreenData } from "@applicaster/zapp-react-native-utils/reactHooks";
5
5
  import { isFirstComponentScreenPicker } from "@applicaster/zapp-react-native-utils/componentsUtils";
6
+ import { toNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
6
7
 
7
8
  interface IProps {
8
9
  targetScreenId?: string;
9
10
  children?: React.ReactNode;
10
11
  style?: ViewStyle;
11
- extraOffset?: number;
12
+ extraVerticalOffset: Option<number>;
12
13
  }
13
14
 
14
- type CombinedProps = IProps & ViewProps;
15
15
  /**
16
16
  * The MarginTop is essentially a feature used for managing the visibility of components on your screen.
17
17
  * A more accurate term for this might be something like a 'component visibility threshold' or 'cut-off point'.
@@ -47,7 +47,7 @@ export const useMarginTop = (targetScreenId: string): number => {
47
47
 
48
48
  // Empty string means that value is blank in the CMS. Fallback to theme
49
49
  if (String(screenData?.styles?.screen_margin_top) === "") {
50
- return Number(theme.screen_margin_top);
50
+ return toNumberWithDefault(0, theme.screen_margin_top);
51
51
  }
52
52
 
53
53
  /**
@@ -58,28 +58,29 @@ export const useMarginTop = (targetScreenId: string): number => {
58
58
  */
59
59
  if (screenData?.styles?.screen_margin_top === undefined) {
60
60
  if (isGeneralContentScreen || supportsUiComponents) {
61
- return Number(theme.screen_margin_top);
61
+ return toNumberWithDefault(0, theme.screen_margin_top);
62
62
  }
63
63
 
64
64
  return 0;
65
65
  }
66
66
 
67
- return Number(screenData?.styles?.screen_margin_top);
67
+ return toNumberWithDefault(0, screenData?.styles?.screen_margin_top);
68
68
  };
69
69
 
70
- export const TopMarginApplicator: React.FC<CombinedProps> = (
71
- props: CombinedProps
72
- ) => {
73
- const extraOffset = props?.extraOffset ?? 0;
70
+ export const TopMarginApplicator: React.FC<IProps> = ({
71
+ targetScreenId,
72
+ style,
73
+ children,
74
+ extraVerticalOffset,
75
+ }: IProps) => {
76
+ const extraOffset = toNumberWithDefault(0, extraVerticalOffset);
74
77
 
75
78
  // HACK: Remove extraOffset when focusIssue with absolute elements is fixed on tvos
76
- const marginTop = useMarginTop(props.targetScreenId) + extraOffset;
77
- const style = { ...((props.style as {}) || {}), marginTop };
79
+ const marginTop = useMarginTop(targetScreenId);
78
80
 
79
- // Then, spread the rest of the props on your returned JSX.
80
81
  return (
81
- <View {...props} style={style}>
82
- {props.children}
82
+ <View style={[style, { marginTop: marginTop + extraOffset }]}>
83
+ {children}
83
84
  </View>
84
85
  );
85
86
  };
@@ -36,6 +36,7 @@ export function CurrentScreenContextProvider({
36
36
  screenData: NavigationScreenData;
37
37
  }) {
38
38
  const { pathname, isActive = false, screenData } = props;
39
+ console.log("CurrentScreenContextProvider", { screenData });
39
40
 
40
41
  const [initialScreenData, setInitialScreenData] = React.useState(screenData);
41
42
 
@@ -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,