@applicaster/zapp-react-native-ui-components 12.0.0-alpha.5471145113 → 12.0.0-alpha.5522474195

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 (30) hide show
  1. package/Components/BaseFocusable/index.tsx +2 -2
  2. package/Components/Cell/CellWithFocusable.tsx +6 -4
  3. package/Components/Cell/TvOSCellComponent.tsx +3 -7
  4. package/Components/Focusable/Focusable.tsx +6 -1
  5. package/Components/FocusableGroup/index.tsx +10 -2
  6. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +7 -2
  7. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -1
  8. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.tsx +6 -1
  9. package/Components/MasterCell/utils/__tests__/insertBetween.test.js +9 -0
  10. package/Components/MasterCell/utils/index.ts +5 -1
  11. package/Components/Screen/TV/index.web.tsx +9 -2
  12. package/Components/VideoLive/LiveImageManager.ts +10 -0
  13. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +48 -38
  14. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +40 -33
  15. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +98 -81
  16. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +13 -5
  17. package/Components/VideoModal/ModalAnimation/utils.ts +6 -4
  18. package/Components/VideoModal/PlayerWrapper.tsx +1 -1
  19. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +32 -12
  20. package/Components/VideoModal/utils.ts +5 -2
  21. package/Contexts/FocusableGroupContext/FocusableGroupContext.ts +3 -0
  22. package/Contexts/FocusableGroupContext/FocusableGroupContextProvider.tsx +16 -0
  23. package/Contexts/FocusableGroupContext/__tests__/FocusableGroupContextProvider.test.tsx +22 -0
  24. package/Contexts/FocusableGroupContext/__tests__/withFocusableContext.test.tsx +26 -0
  25. package/Contexts/FocusableGroupContext/index.ts +7 -0
  26. package/Contexts/FocusableGroupContext/withFocusableContext.tsx +17 -0
  27. package/Contexts/ScreenContext/index.tsx +0 -6
  28. package/package.json +5 -5
  29. /package/Components/FocusableGroup/{index.android.js → index.android.ts} +0 -0
  30. /package/Components/FocusableGroup/{index.ios.js → index.ios.ts} +0 -0
@@ -71,9 +71,9 @@ export class BaseFocusable<
71
71
  }
72
72
 
73
73
  getId(props?: { id: string; groupId: string }) {
74
- const { id, groupId = null } = props || this.props;
74
+ const { id } = props || this.props;
75
75
 
76
- return groupId ? `${groupId}-${id}` : id;
76
+ return id;
77
77
  }
78
78
 
79
79
  getRect() {
@@ -3,6 +3,8 @@ import * as React from "react";
3
3
  import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
4
4
  import * as FOCUS_EVENTS from "@applicaster/zapp-react-native-utils/appUtils/focusManager/events";
5
5
  import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
6
+ import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
7
+
6
8
  import { isAppleTV } from "../../Helpers/Platform";
7
9
  import { useCellState } from "../MasterCell/utils";
8
10
 
@@ -54,7 +56,7 @@ type Props = {
54
56
  skipFocusManagerRegistration?: boolean;
55
57
  isFocusable?: boolean;
56
58
  behavior: Behavior;
57
- selected?: boolean;
59
+ focused?: boolean;
58
60
  };
59
61
 
60
62
  export function CellWithFocusable(props: Props) {
@@ -70,7 +72,7 @@ export function CellWithFocusable(props: Props) {
70
72
  skipFocusManagerRegistration,
71
73
  isFocusable,
72
74
  behavior,
73
- selected,
75
+ focused,
74
76
  } = props;
75
77
 
76
78
  const isFocused = useCellFocusedState(
@@ -82,8 +84,7 @@ export function CellWithFocusable(props: Props) {
82
84
  const state = useCellState({
83
85
  id: item.id,
84
86
  behavior,
85
- focused: isFocused,
86
- selected,
87
+ focused: isFocused || toBooleanWithDefaultFalse(focused),
87
88
  });
88
89
 
89
90
  const [focusedButtonId, setFocusedButtonId] = React.useState(undefined);
@@ -114,6 +115,7 @@ export function CellWithFocusable(props: Props) {
114
115
  preferredFocus={preferredFocus}
115
116
  skipFocusManagerRegistration={skipFocusManagerRegistration}
116
117
  isFocusable={isFocusable}
118
+ focused={focused}
117
119
  />
118
120
  );
119
121
  }
@@ -226,13 +226,7 @@ export class TvOSCellComponent extends React.Component<Props, State> {
226
226
 
227
227
  hasReceivedFocus() {}
228
228
 
229
- scrollTo(index) {
230
- // Workaround for HL autoscroll/autoloop feature, we scroll manually on tvOS.
231
- // Should be removed after refactoring autoscroll/autoloop feature
232
- if (this.props.component?.component_type !== "horizontal_list_qb") {
233
- this.props?.scrollToIndex?.(index);
234
- }
235
- }
229
+ scrollTo() {}
236
230
 
237
231
  render() {
238
232
  const {
@@ -279,7 +273,9 @@ export class TvOSCellComponent extends React.Component<Props, State> {
279
273
  index={index}
280
274
  scrollTo={this.scrollTo}
281
275
  preferredFocus={preferredFocus}
276
+ focused={this.props.focused}
282
277
  behavior={behavior}
278
+ isFocusable={isFocusable}
283
279
  />
284
280
  </View>
285
281
  );
@@ -4,6 +4,7 @@ import * as R from "ramda";
4
4
  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
+ import { withFocusableContext } from "../../Contexts/FocusableGroupContext/withFocusableContext";
7
8
 
8
9
  type Props = {
9
10
  initialFocus?: boolean;
@@ -23,7 +24,7 @@ type Props = {
23
24
  style?: React.CSSProperties;
24
25
  };
25
26
 
26
- export class Focusable extends BaseFocusable<Props> {
27
+ class Focusable extends BaseFocusable<Props> {
27
28
  isGroup: boolean;
28
29
  mouse: boolean;
29
30
  longPressTimeout = null;
@@ -198,3 +199,7 @@ export class Focusable extends BaseFocusable<Props> {
198
199
  );
199
200
  }
200
201
  }
202
+
203
+ const FocusableWithFocusableContext = withFocusableContext(Focusable);
204
+
205
+ export { FocusableWithFocusableContext as Focusable };
@@ -1,6 +1,8 @@
1
1
  import * as React from "react";
2
2
 
3
3
  import { BaseFocusable } from "../BaseFocusable";
4
+ import { FocusableGroupContextProvider } from "../../Contexts/FocusableGroupContext";
5
+ import { withFocusableContext } from "../../Contexts/FocusableGroupContext/withFocusableContext";
4
6
 
5
7
  type Props = {
6
8
  testID?: string;
@@ -29,7 +31,7 @@ type Props = {
29
31
  excludeFromFocusSearching?: boolean;
30
32
  };
31
33
 
32
- export class FocusableGroup extends BaseFocusable<Props> {
34
+ class FocusableGroup extends BaseFocusable<Props> {
33
35
  constructor(props) {
34
36
  super(props);
35
37
  this.isGroup = true;
@@ -82,8 +84,14 @@ export class FocusableGroup extends BaseFocusable<Props> {
82
84
  style={style}
83
85
  ref={this.ref}
84
86
  >
85
- {children}
87
+ <FocusableGroupContextProvider id={id}>
88
+ {children}
89
+ </FocusableGroupContextProvider>
86
90
  </div>
87
91
  );
88
92
  }
89
93
  }
94
+
95
+ const FocusableGroupWithFocusableContext = withFocusableContext(FocusableGroup);
96
+
97
+ export { FocusableGroupWithFocusableContext as FocusableGroup };
@@ -22,6 +22,10 @@ const styles = StyleSheet.create({
22
22
  borderWidth: 0,
23
23
  borderColor: COLOR_TRANSPARENT,
24
24
  },
25
+ border: {
26
+ ...StyleSheet.absoluteFillObject,
27
+ zIndex: 1,
28
+ },
25
29
  });
26
30
 
27
31
  const getBorderPadding = (borderPosition: BorderPosition, borderWidth) => {
@@ -87,8 +91,9 @@ const Border = ({
87
91
 
88
92
  return (
89
93
  <View
94
+ pointerEvents="none"
90
95
  style={{
91
- ...StyleSheet.absoluteFillObject,
96
+ ...styles.border,
92
97
  right: -containerPadding - paddingRight,
93
98
  bottom: -containerPadding - paddingBottom,
94
99
  left: -containerPadding - paddingLeft,
@@ -130,8 +135,8 @@ export const BorderContainerView = (props: Props) => {
130
135
  {...props}
131
136
  style={[style, styles.container, borderStyleForMissingBorder]}
132
137
  >
133
- {children}
134
138
  <Border style={style} padding={padding} borderPosition={borderPosition} />
139
+ {children}
135
140
  </View>
136
141
  );
137
142
  };
@@ -4,6 +4,7 @@ import { Focusable } from "@applicaster/zapp-react-native-ui-components/Componen
4
4
  import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
5
5
  import * as R from "ramda";
6
6
  import { getXray } from "@applicaster/zapp-react-native-utils/logger";
7
+ import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
7
8
 
8
9
  const { Logger } = getXray();
9
10
 
@@ -35,7 +36,9 @@ export function FocusableView({ style, children, item, ...otherProps }: Props) {
35
36
 
36
37
  const focusableId = getFocusableId(prefixId, suffixId);
37
38
 
38
- const focused = focusableId === focusedButtonId;
39
+ const focused =
40
+ focusableId === focusedButtonId ||
41
+ toBooleanWithDefaultFalse(otherProps.focused);
39
42
 
40
43
  const additionalStyles = focused ? focusedStyles : normalStyles;
41
44
 
@@ -7,6 +7,10 @@ import type {
7
7
  ContainerProps,
8
8
  ContainerChildren,
9
9
  } from "./types";
10
+ import {
11
+ useIsRTL,
12
+ applyRTLStylesIfNeeded,
13
+ } from "@applicaster/zapp-react-native-utils/localizationUtils";
10
14
 
11
15
  const HorizontalSeparator = ({ width }: HorizontalSeparatorProps) => (
12
16
  <View style={{ width }} />
@@ -17,6 +21,7 @@ export function ButtonContainerView({
17
21
  children,
18
22
  ...otherProps
19
23
  }: ContainerProps) {
24
+ const isRTL = useIsRTL();
20
25
  const horizontalGutter = R.pathOr(0, ["horizontalGutter"], otherProps);
21
26
 
22
27
  const filteredChildren = useFilterChildren<ContainerChildren>(children);
@@ -26,7 +31,7 @@ export function ButtonContainerView({
26
31
  }
27
32
 
28
33
  return (
29
- <View style={style}>
34
+ <View style={applyRTLStylesIfNeeded(style, isRTL)}>
30
35
  {insertBetween(
31
36
  (index) => (
32
37
  <HorizontalSeparator
@@ -9,4 +9,13 @@ describe("insertBetween", () => {
9
9
 
10
10
  expect(result).toEqual([1, "|", 2, "|", 3]);
11
11
  });
12
+
13
+ it("insertBetween", () => {
14
+ const separator = (index) => `separator:${index}`;
15
+ const items = [1, 2, 3];
16
+
17
+ const result = insertBetween(separator, items);
18
+
19
+ expect(result).toEqual([1, "separator:0", 2, "separator:1", 3]);
20
+ });
12
21
  });
@@ -1,5 +1,6 @@
1
1
  import React, { useEffect, useMemo } from "react";
2
2
  import * as R from "ramda";
3
+ import validateColor from "validate-color";
3
4
  import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
4
5
  import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
5
6
 
@@ -52,7 +53,10 @@ function resolveColorForProp(entry, style, colorProp) {
52
53
  const colorFromProp = style[colorProp];
53
54
  const nestedEntryValue = R.path(colorFromProp.split("."), entry);
54
55
 
55
- if (nestedEntryValue === undefined) {
56
+ if (
57
+ nestedEntryValue === undefined &&
58
+ !validateColor(colorFromProp.replace(".00", "")) // https://github.com/dreamyguy/validate-color/issues/44
59
+ ) {
56
60
  logWarning(colorFromProp, style, entry, nestedEntryValue);
57
61
  }
58
62
 
@@ -42,6 +42,13 @@ const getPluginType = pathOr("unknown_plugin_type", [
42
42
  "plugin_type",
43
43
  ]);
44
44
 
45
+ export const isPresentFullScreen = pathOr(false, [
46
+ "state",
47
+ "screen",
48
+ "module",
49
+ "presentFullScreen",
50
+ ]);
51
+
45
52
  const isPlayerPlugin = (routeState): boolean =>
46
53
  getPluginType(routeState) === "player";
47
54
 
@@ -150,8 +157,9 @@ export const Screen = ({ route, Components }: Props) => {
150
157
 
151
158
  const isPlayer = isPlayerPlugin(routeState);
152
159
  const isLogin = isLoginPlugin(routeState);
160
+ const isFullScreen = isPresentFullScreen(routeState);
153
161
 
154
- const isNavBarVisible = !isPlayer && !isLogin;
162
+ const isNavBarVisible = !isPlayer && !isLogin && !isFullScreen;
155
163
 
156
164
  const screenData =
157
165
  screenType === "hooks" || isPlayer
@@ -162,7 +170,6 @@ export const Screen = ({ route, Components }: Props) => {
162
170
  <View style={[styles.container, { backgroundColor }]}>
163
171
  <NavBarContainer isVisible={isNavBarVisible} onReady={noop}>
164
172
  <NavBar
165
- groupId={route}
166
173
  isVisible={isNavBarVisible}
167
174
  menuPlugin={menuPlugin}
168
175
  navigationProps={navigationProps}
@@ -525,6 +525,16 @@ export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
525
525
  LiveImageManager.instance.onLiveImageError(this, error);
526
526
  };
527
527
 
528
+ onVideoError = (error: Error) => {
529
+ log_error(
530
+ `onVideoError: live-image, fail to play, switching back to image. error: ${
531
+ error?.message
532
+ }, ${playerInfo(this.player)}`
533
+ );
534
+
535
+ LiveImageManager.instance.onLiveImageError(this, error);
536
+ };
537
+
528
538
  onVideoEnd = (_event) => {
529
539
  log_info(
530
540
  `onVideoEnd: live-image, video completed, switching back to image, ${playerInfo(
@@ -100,58 +100,68 @@ export const AnimatedScrollModalComponent = ({ children }: Props) => {
100
100
  { useNativeDriver: true }
101
101
  );
102
102
 
103
- const onHandlerStateChange = ({ nativeEvent }) => {
104
- if (nativeEvent.oldState === State.ACTIVE && scrollPosition.current === 0) {
105
- // eslint-disable-next-line prefer-const
106
- const { velocityY, translationY } = nativeEvent;
107
- const dragToss = 0.05;
103
+ const onHandlerStateChange = React.useCallback(
104
+ ({ nativeEvent }) => {
105
+ if (
106
+ nativeEvent.oldState === State.ACTIVE &&
107
+ scrollPosition.current === 0
108
+ ) {
109
+ // eslint-disable-next-line prefer-const
110
+ const { velocityY, translationY } = nativeEvent;
111
+ const dragToss = 0.05;
108
112
 
109
- const preparedTranslationY =
110
- Math.abs(translationY) - lastScrollYValue.current;
113
+ const preparedTranslationY =
114
+ Math.abs(translationY) - lastScrollYValue.current;
111
115
 
112
- if (videoModalState.mode === "MAXIMIZED") {
113
- const endOffsetY =
114
- lastSnap + preparedTranslationY + dragToss * velocityY + 150;
116
+ if (videoModalState.mode === "MAXIMIZED") {
117
+ const endOffsetY =
118
+ lastSnap + preparedTranslationY + dragToss * velocityY + 150;
115
119
 
116
- let destSnapPoint = modalSnapPoints[0];
120
+ let destSnapPoint = modalSnapPoints[0];
117
121
 
118
- for (const snapPoint of modalSnapPoints) {
119
- const distFromSnap = Math.abs(snapPoint - endOffsetY);
122
+ for (const snapPoint of modalSnapPoints) {
123
+ const distFromSnap = Math.abs(snapPoint - endOffsetY);
120
124
 
121
- if (distFromSnap < Math.abs(destSnapPoint - endOffsetY)) {
122
- destSnapPoint = snapPoint;
125
+ if (distFromSnap < Math.abs(destSnapPoint - endOffsetY)) {
126
+ destSnapPoint = snapPoint;
127
+ }
123
128
  }
124
- }
125
129
 
126
- setLastSnap(destSnapPoint);
130
+ setLastSnap(destSnapPoint);
127
131
 
128
- if (destSnapPoint === modalSnapPoints[0]) {
129
- translateYOffset.extractOffset();
130
- translateYOffset.setValue(preparedTranslationY);
131
- translateYOffset.flattenOffset();
132
- dragScrollY.setValue(0);
132
+ if (destSnapPoint === modalSnapPoints[0]) {
133
+ translateYOffset.extractOffset();
134
+ translateYOffset.setValue(preparedTranslationY);
135
+ translateYOffset.flattenOffset();
136
+ dragScrollY.setValue(0);
133
137
 
134
- setPlayerAnimationState(PlayerAnimationStateEnum.maximaze);
138
+ setPlayerAnimationState(PlayerAnimationStateEnum.maximaze);
139
+ } else {
140
+ setPlayerAnimationState(PlayerAnimationStateEnum.minimize);
141
+ }
135
142
  } else {
136
- setPlayerAnimationState(PlayerAnimationStateEnum.minimize);
143
+ if (translationY < 0) {
144
+ // from mini to full
145
+ setLastSnap(modalSnapPoints[0]);
146
+
147
+ translateYOffset.setValue(
148
+ modalSnapPoints[1] - preparedTranslationY
149
+ );
150
+
151
+ dragScrollY.setValue(0);
152
+
153
+ setPlayerAnimationState(PlayerAnimationStateEnum.maximaze);
154
+ } else {
155
+ resetPlayerAnimationState();
156
+ }
137
157
  }
138
158
  } else {
139
- if (translationY < 0) {
140
- // from mini to full
141
- setLastSnap(modalSnapPoints[0]);
142
- translateYOffset.setValue(modalSnapPoints[1] - preparedTranslationY);
143
- dragScrollY.setValue(0);
144
-
145
- setPlayerAnimationState(PlayerAnimationStateEnum.maximaze);
146
- } else {
159
+ playerAnimationState === PlayerAnimationStateEnum.drag_scroll &&
147
160
  resetPlayerAnimationState();
148
- }
149
161
  }
150
- } else {
151
- playerAnimationState === PlayerAnimationStateEnum.drag_scroll &&
152
- resetPlayerAnimationState();
153
- }
154
- };
162
+ },
163
+ [lastSnap, modalSnapPoints, playerAnimationState, videoModalState.mode]
164
+ );
155
165
 
156
166
  const onScroll = React.useCallback(({ nativeEvent }) => {
157
167
  scrollPosition.current = nativeEvent.contentOffset.y;
@@ -50,50 +50,57 @@ export const AnimatedVideoPlayer = ({ children }: Props) => {
50
50
  { useNativeDriver: true }
51
51
  );
52
52
 
53
- const onHandlerStateChange = ({ nativeEvent }) => {
54
- if (nativeEvent.oldState === State.ACTIVE) {
55
- // eslint-disable-next-line prefer-const
56
- const { velocityY, translationY } = nativeEvent;
57
- const preparedTranslationY = Math.abs(translationY);
58
- const dragToss = 0.05;
59
-
60
- if (videoModalState.mode === "MAXIMIZED") {
61
- if (translationY > 0) {
62
- const endOffsetY =
63
- lastSnap + preparedTranslationY + dragToss * velocityY + 200;
64
-
65
- let destSnapPoint = modalSnapPoints[0];
53
+ const onHandlerStateChange = React.useCallback(
54
+ ({ nativeEvent }) => {
55
+ if (nativeEvent.oldState === State.ACTIVE) {
56
+ // eslint-disable-next-line prefer-const
57
+ const { velocityY, translationY } = nativeEvent;
58
+ const preparedTranslationY = Math.abs(translationY);
59
+ const dragToss = 0.05;
60
+
61
+ if (videoModalState.mode === "MAXIMIZED") {
62
+ if (translationY > 0) {
63
+ const endOffsetY =
64
+ lastSnap + preparedTranslationY + dragToss * velocityY + 200;
65
+
66
+ let destSnapPoint = modalSnapPoints[0];
67
+
68
+ for (const snapPoint of modalSnapPoints) {
69
+ const distFromSnap = Math.abs(snapPoint - endOffsetY);
70
+
71
+ if (distFromSnap < Math.abs(destSnapPoint - endOffsetY)) {
72
+ destSnapPoint = snapPoint;
73
+ }
74
+ }
66
75
 
67
- for (const snapPoint of modalSnapPoints) {
68
- const distFromSnap = Math.abs(snapPoint - endOffsetY);
76
+ setLastSnap(destSnapPoint);
69
77
 
70
- if (distFromSnap < Math.abs(destSnapPoint - endOffsetY)) {
71
- destSnapPoint = snapPoint;
78
+ if (destSnapPoint === modalSnapPoints[0]) {
79
+ setPlayerAnimationState(PlayerAnimationStateEnum.maximaze);
80
+ } else {
81
+ lastScrollY.setValue(0);
82
+ lastScrollYValue.current = 0;
83
+ setPlayerAnimationState(PlayerAnimationStateEnum.minimize);
72
84
  }
73
85
  }
86
+ } else if (videoModalState.mode === "MINIMIZED") {
87
+ if (translationY < 0) {
88
+ // from mini to full
89
+ setLastSnap(modalSnapPoints[0]);
74
90
 
75
- setLastSnap(destSnapPoint);
91
+ translateYOffset.setValue(
92
+ modalSnapPoints[1] - preparedTranslationY
93
+ );
76
94
 
77
- if (destSnapPoint === modalSnapPoints[0]) {
78
95
  setPlayerAnimationState(PlayerAnimationStateEnum.maximaze);
79
96
  } else {
80
- lastScrollY.setValue(0);
81
- lastScrollYValue.current = 0;
82
- setPlayerAnimationState(PlayerAnimationStateEnum.minimize);
97
+ resetPlayerAnimationState();
83
98
  }
84
99
  }
85
- } else if (videoModalState.mode === "MINIMIZED") {
86
- if (translationY < 0) {
87
- // from mini to full
88
- setLastSnap(modalSnapPoints[0]);
89
- translateYOffset.setValue(modalSnapPoints[1] - preparedTranslationY);
90
- setPlayerAnimationState(PlayerAnimationStateEnum.maximaze);
91
- } else {
92
- resetPlayerAnimationState();
93
- }
94
100
  }
95
- }
96
- };
101
+ },
102
+ [lastSnap, modalSnapPoints, videoModalState.mode]
103
+ );
97
104
 
98
105
  React.useEffect(() => {
99
106
  const dragVideoPlayerYListenerId = dragVideoPlayerY.addListener(
@@ -1,10 +1,7 @@
1
1
  import React, { useRef, useState } from "react";
2
- import { Animated, View, StyleSheet } from "react-native";
2
+ import { Animated, View } from "react-native";
3
3
 
4
- import {
5
- useSafeAreaInsets,
6
- useSafeAreaFrame,
7
- } from "react-native-safe-area-context";
4
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
8
5
 
9
6
  import {
10
7
  useModalAnimationContext,
@@ -18,6 +15,7 @@ import {
18
15
  } from "@applicaster/zapp-react-native-utils/reactHooks";
19
16
  import { useIsRTL } from "@applicaster/zapp-react-native-utils/localizationUtils";
20
17
  import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
18
+ import { useAppState } from "@applicaster/zapp-react-native-utils/reactHooks/app";
21
19
 
22
20
  import {
23
21
  gestureListenerHelper,
@@ -35,10 +33,6 @@ type Props = {
35
33
  additionalData?: { [key: string]: any };
36
34
  };
37
35
 
38
- const styles = StyleSheet.create({
39
- flex: { flex: 1 },
40
- });
41
-
42
36
  export const AnimationView = ({
43
37
  animationType,
44
38
  style,
@@ -56,21 +50,21 @@ export const AnimationView = ({
56
50
  tabletLandscapePlayerTopPosition,
57
51
  setTabletLandscapePlayerTopPosition,
58
52
  modalSnapPoints,
53
+ startComponentsAnimationDistance,
59
54
  } = useModalAnimationContext();
60
55
 
61
56
  const isRTL = useIsRTL();
57
+ const isAppActive = useAppState(true);
62
58
 
63
59
  const {
64
- height: screenHeight,
60
+ width: screenWidth,
65
61
  deviceInfo: { isTablet, isTabletLandscape },
66
- } = useDimensions("screen", { deviceInfo: true });
62
+ } = useDimensions("window", { deviceInfo: true });
67
63
 
68
- const startAnimationDistance = Math.round((screenHeight * 60) / 100);
69
64
  const { videoModalState } = useNavigation();
70
65
  const bottomTabBarHeight = useGetBottomTabBarHeight();
71
66
  const minimisedWidth = defaultAspectRatioWidth(minimisedHeight);
72
67
  const insets = useSafeAreaInsets();
73
- const { width: screenWidth } = useSafeAreaFrame();
74
68
  const animationComponentRef = useRef(null);
75
69
 
76
70
  // retrieving top position of the player for tablet landscape mode
@@ -88,13 +82,6 @@ export const AnimationView = ({
88
82
  getAnimationDefaultValue(animationType, bottomTabBarHeight)
89
83
  );
90
84
 
91
- const animationValueType: string =
92
- animationType === ComponentAnimationType.player ? "ValueXY" : "Value";
93
-
94
- const animatedValue = useRef(
95
- new Animated[animationValueType](defaultValue)
96
- ).current;
97
-
98
85
  const isAudioItem = React.useMemo(
99
86
  () =>
100
87
  videoModalState.item?.content?.type?.includes?.("audio") ||
@@ -118,63 +105,8 @@ export const AnimationView = ({
118
105
  : -additionalData.moveValue
119
106
  : 0;
120
107
 
121
- const calculationData = React.useMemo(
122
- () => ({
123
- isAudioItem,
124
- bottomTabBarHeight,
125
- minimisedHeight,
126
- minimisedWidth,
127
- defaultValue,
128
- moveUpValue,
129
- moveComponentHorizontalValue,
130
- isTablet,
131
- isTabletLandscape,
132
- isRTL,
133
- fromMiniPlayer: videoModalState.mode === "MINIMIZED",
134
- inlineAudioPlayer,
135
- }),
136
- [
137
- bottomTabBarHeight,
138
- minimisedHeight,
139
- minimisedWidth,
140
- defaultValue,
141
- moveUpValue,
142
- moveComponentHorizontalValue,
143
- videoModalState.mode,
144
- ]
145
- );
146
-
147
- React.useEffect(() => {
148
- if (additionalData.resetAnimationValue) {
149
- animatedValue.setValue(0);
150
- }
151
- }, [additionalData.resetAnimationValue]);
152
-
153
- React.useEffect(() => {
154
- if (animationType === ComponentAnimationType.player) {
155
- let width, height;
156
-
157
- if (additionalData.aspectRatio) {
158
- width = isTabletLandscape ? additionalData.width : screenWidth;
159
-
160
- height =
161
- (isTabletLandscape ? additionalData.width : screenWidth) /
162
- additionalData.aspectRatio;
163
- } else {
164
- width =
165
- typeof additionalData.width === "number" && additionalData.width;
166
-
167
- height =
168
- typeof additionalData.height === "number" && additionalData.height;
169
- }
170
-
171
- if (defaultValue.x === 0 && width && height) {
172
- const value = { x: width, y: height };
173
- setDefaultValue(value);
174
- animatedValue.setValue(value);
175
- }
176
- }
177
- }, [additionalData, screenWidth]);
108
+ const animationValueType: string =
109
+ animationType === ComponentAnimationType.player ? "ValueXY" : "Value";
178
110
 
179
111
  const getAnimationValue = React.useCallback(
180
112
  (animationType, state) => {
@@ -292,6 +224,90 @@ export const AnimationView = ({
292
224
  ]
293
225
  );
294
226
 
227
+ const getInitialValue = React.useCallback(() => {
228
+ const mode =
229
+ videoModalState.mode === "PIP"
230
+ ? videoModalState.previousMode
231
+ : videoModalState.mode;
232
+
233
+ switch (mode) {
234
+ case "MINIMIZED":
235
+ return getAnimationValue(
236
+ animationType,
237
+ PlayerAnimationStateEnum.minimize
238
+ ).toValue;
239
+
240
+ case "MAXIMIZED":
241
+ default:
242
+ return getAnimationValue(
243
+ animationType,
244
+ PlayerAnimationStateEnum.maximaze
245
+ ).toValue;
246
+ }
247
+ }, [defaultValue, videoModalState]);
248
+
249
+ const animatedValue = useRef(
250
+ new Animated[animationValueType](getInitialValue())
251
+ ).current;
252
+
253
+ const calculationData = React.useMemo(
254
+ () => ({
255
+ isAudioItem,
256
+ bottomTabBarHeight,
257
+ minimisedHeight,
258
+ minimisedWidth,
259
+ defaultValue,
260
+ moveUpValue,
261
+ moveComponentHorizontalValue,
262
+ isTablet,
263
+ isTabletLandscape,
264
+ isRTL,
265
+ fromMiniPlayer: videoModalState.mode === "MINIMIZED",
266
+ inlineAudioPlayer,
267
+ }),
268
+ [
269
+ bottomTabBarHeight,
270
+ minimisedHeight,
271
+ minimisedWidth,
272
+ defaultValue,
273
+ moveUpValue,
274
+ moveComponentHorizontalValue,
275
+ videoModalState.mode,
276
+ ]
277
+ );
278
+
279
+ React.useEffect(() => {
280
+ if (additionalData.resetAnimationValue) {
281
+ animatedValue.setValue(defaultValue);
282
+ }
283
+ }, [additionalData.resetAnimationValue, defaultValue]);
284
+
285
+ React.useEffect(() => {
286
+ if (animationType === ComponentAnimationType.player) {
287
+ let width, height;
288
+
289
+ if (additionalData.aspectRatio) {
290
+ width = isTabletLandscape ? additionalData.width : screenWidth;
291
+
292
+ height =
293
+ (isTabletLandscape ? additionalData.width : screenWidth) /
294
+ additionalData.aspectRatio;
295
+ } else {
296
+ width =
297
+ typeof additionalData.width === "number" && additionalData.width;
298
+
299
+ height =
300
+ typeof additionalData.height === "number" && additionalData.height;
301
+ }
302
+
303
+ if (defaultValue.x === 0 && width && height) {
304
+ const value = { x: width, y: height };
305
+ setDefaultValue(value);
306
+ animatedValue.setValue(value);
307
+ }
308
+ }
309
+ }, [additionalData, screenWidth]);
310
+
295
311
  React.useEffect(() => {
296
312
  if (
297
313
  playerAnimationState === PlayerAnimationStateEnum.minimize &&
@@ -325,7 +341,7 @@ export const AnimationView = ({
325
341
  animatedValue,
326
342
  calculationData,
327
343
  modalSnapPoint: modalSnapPoints[1],
328
- startAnimationDistance,
344
+ startComponentsAnimationDistance,
329
345
  startComponentsAnimation,
330
346
  setStartComponentsAnimation,
331
347
  getAnimationValue,
@@ -350,7 +366,7 @@ export const AnimationView = ({
350
366
  animatedValue,
351
367
  calculationData,
352
368
  modalSnapPoint: modalSnapPoints[1],
353
- startAnimationDistance,
369
+ startComponentsAnimationDistance,
354
370
  startComponentsAnimation,
355
371
  setStartComponentsAnimation,
356
372
  getAnimationValue,
@@ -372,9 +388,9 @@ export const AnimationView = ({
372
388
  ref={animationComponentRef}
373
389
  style={[
374
390
  ...preparedStyles,
375
- additionalData.saveArea ? styles.flex : null,
376
391
  (videoModalState.mode === "MAXIMIZED" ||
377
- videoModalState.mode === "MINIMIZED") &&
392
+ videoModalState.mode === "MINIMIZED" ||
393
+ (videoModalState.mode === "PIP" && isAppActive)) &&
378
394
  getAnimationStyle(animationType, animatedValue),
379
395
  ]}
380
396
  >
@@ -412,6 +428,7 @@ export const AnimationComponent = (props: Props) => {
412
428
  delete additionalData.extraAnimation[animationType];
413
429
 
414
430
  return (
431
+ // @ts-ignore
415
432
  <Component {...(isReactFragment ? null : props)}>
416
433
  <AnimationComponent
417
434
  {...animationProps}
@@ -1,11 +1,11 @@
1
1
  import React, { useContext } from "react";
2
2
  import { Animated } from "react-native";
3
3
 
4
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
4
5
  import {
5
- useSafeAreaFrame,
6
- useSafeAreaInsets,
7
- } from "react-native-safe-area-context";
8
- import { useGetBottomTabBarHeight } from "@applicaster/zapp-react-native-utils/reactHooks";
6
+ useGetBottomTabBarHeight,
7
+ useDimensions,
8
+ } from "@applicaster/zapp-react-native-utils/reactHooks";
9
9
 
10
10
  import { useConfiguration } from "../utils";
11
11
 
@@ -39,6 +39,7 @@ export type ModalAnimationContextT = {
39
39
  setLastSnap: (value: number) => void;
40
40
  tabletLandscapePlayerTopPosition: number;
41
41
  setTabletLandscapePlayerTopPosition: (value: number) => void;
42
+ startComponentsAnimationDistance: number;
42
43
  };
43
44
 
44
45
  const ReactContext = React.createContext<ModalAnimationContextT>({
@@ -62,6 +63,7 @@ const ReactContext = React.createContext<ModalAnimationContextT>({
62
63
  setLastSnap: () => null,
63
64
  tabletLandscapePlayerTopPosition: 0,
64
65
  setTabletLandscapePlayerTopPosition: () => null,
66
+ startComponentsAnimationDistance: 0,
65
67
  });
66
68
 
67
69
  export function useModalAnimationContext() {
@@ -93,10 +95,15 @@ const Provider = ({ children }: { children: React.ReactNode }) => {
93
95
  const translateYOffset = React.useRef(new Animated.Value(0)).current;
94
96
  const dragVideoPlayerY = React.useRef(new Animated.Value(0)).current;
95
97
 
96
- const { height } = useSafeAreaFrame();
98
+ const { height } = useDimensions("window", { fullDimensions: true });
97
99
  const { bottom: bottomSafeArea } = useSafeAreaInsets();
98
100
  const bottomTabBarHeight = useGetBottomTabBarHeight();
99
101
 
102
+ const startComponentsAnimationDistance = React.useMemo(
103
+ () => Math.round((height * 60) / 100),
104
+ [height]
105
+ );
106
+
100
107
  /*
101
108
  If bottomTabBarHeight is equal 0 it means an app does not use bottomTabBar.
102
109
  Because of this we need to minus bottom SafeArea offset.
@@ -134,6 +141,7 @@ const Provider = ({ children }: { children: React.ReactNode }) => {
134
141
  setLastSnap,
135
142
  tabletLandscapePlayerTopPosition,
136
143
  setTabletLandscapePlayerTopPosition,
144
+ startComponentsAnimationDistance,
137
145
  }}
138
146
  >
139
147
  {children}
@@ -233,7 +233,7 @@ export const gestureListenerHelper = (data) => {
233
233
  animatedValue,
234
234
  calculationData,
235
235
  modalSnapPoint,
236
- startAnimationDistance,
236
+ startComponentsAnimationDistance,
237
237
  startComponentsAnimation,
238
238
  setStartComponentsAnimation,
239
239
  getAnimationValue,
@@ -243,7 +243,7 @@ export const gestureListenerHelper = (data) => {
243
243
  if (listenerValue <= 0) {
244
244
  if (
245
245
  preparedValue >= 0 &&
246
- preparedValue <= modalSnapPoint - startAnimationDistance
246
+ preparedValue <= modalSnapPoint - startComponentsAnimationDistance
247
247
  ) {
248
248
  !startComponentsAnimation && setStartComponentsAnimation(true);
249
249
 
@@ -263,9 +263,11 @@ export const gestureListenerHelper = (data) => {
263
263
  animatedValue.setValue(value);
264
264
  }
265
265
  } else {
266
- if (preparedValue >= startAnimationDistance) {
266
+ if (preparedValue >= startComponentsAnimationDistance) {
267
267
  !startComponentsAnimation && setStartComponentsAnimation(true);
268
- const value = Math.round(preparedValue - startAnimationDistance);
268
+ const value = Math.round(
269
+ preparedValue - startComponentsAnimationDistance
270
+ );
269
271
 
270
272
  animatedValue.setValue(
271
273
  calculateAnimationValue(animationType, value, calculationData)
@@ -197,7 +197,7 @@ export const PlayerWrapper = (props: Props) => {
197
197
  <AnimationComponent
198
198
  animationType={ComponentAnimationType.moveUpComponent}
199
199
  additionalData={{ saveArea: true }}
200
- style={directionStyles(isTabletLandscape)}
200
+ style={[directionStyles(isTabletLandscape), defaultStyles.flex]}
201
201
  >
202
202
  <PathnameContext.Provider value={pathname}>
203
203
  <ScreenContextProvider pathname={pathname}>
@@ -42,9 +42,14 @@ exports[`PlayerWrapper renders inline 1`] = `
42
42
  }
43
43
  animationType="moveUpComponent"
44
44
  style={
45
- Object {
46
- "flexDirection": "column",
47
- }
45
+ Array [
46
+ Object {
47
+ "flexDirection": "column",
48
+ },
49
+ Object {
50
+ "flex": 1,
51
+ },
52
+ ]
48
53
  }
49
54
  >
50
55
  <View
@@ -120,9 +125,14 @@ exports[`PlayerWrapper renders inline and docked 1`] = `
120
125
  }
121
126
  animationType="moveUpComponent"
122
127
  style={
123
- Object {
124
- "flexDirection": "column",
125
- }
128
+ Array [
129
+ Object {
130
+ "flexDirection": "column",
131
+ },
132
+ Object {
133
+ "flex": 1,
134
+ },
135
+ ]
126
136
  }
127
137
  >
128
138
  <View
@@ -198,9 +208,14 @@ exports[`PlayerWrapper renders inline on tablet in landscape orientation 1`] = `
198
208
  }
199
209
  animationType="moveUpComponent"
200
210
  style={
201
- Object {
202
- "flexDirection": "row",
203
- }
211
+ Array [
212
+ Object {
213
+ "flexDirection": "row",
214
+ },
215
+ Object {
216
+ "flex": 1,
217
+ },
218
+ ]
204
219
  }
205
220
  >
206
221
  <View
@@ -292,9 +307,14 @@ exports[`PlayerWrapper renders properly 1`] = `
292
307
  }
293
308
  animationType="moveUpComponent"
294
309
  style={
295
- Object {
296
- "flexDirection": "column",
297
- }
310
+ Array [
311
+ Object {
312
+ "flexDirection": "column",
313
+ },
314
+ Object {
315
+ "flex": 1,
316
+ },
317
+ ]
298
318
  }
299
319
  >
300
320
  <View
@@ -33,8 +33,11 @@ export const useConfiguration = () => {
33
33
  ...pluginConfiguration,
34
34
  });
35
35
 
36
- const { minimised_height, minimised_height_tablet, modal_background_color } =
37
- config;
36
+ const {
37
+ minimised_height = 0,
38
+ minimised_height_tablet = 0,
39
+ modal_background_color,
40
+ } = config;
38
41
 
39
42
  const minimisedHeight = useIsTablet()
40
43
  ? minimised_height_tablet
@@ -0,0 +1,3 @@
1
+ import * as React from "react";
2
+
3
+ export const FocusableGroupContext = React.createContext<string | null>(null);
@@ -0,0 +1,16 @@
1
+ import * as React from "react";
2
+ import { FocusableGroupContext } from "./FocusableGroupContext";
3
+
4
+ type ProviderArgs = {
5
+ id: string;
6
+ children: React.ReactNode;
7
+ };
8
+
9
+ export const FocusableGroupContextProvider = ({
10
+ id,
11
+ children,
12
+ }: ProviderArgs) => (
13
+ <FocusableGroupContext.Provider value={id}>
14
+ {children}
15
+ </FocusableGroupContext.Provider>
16
+ );
@@ -0,0 +1,22 @@
1
+ import React, { useContext } from "react";
2
+ import { Text } from "react-native";
3
+ import { render } from "@testing-library/react-native";
4
+ import { FocusableGroupContextProvider, FocusableGroupContext } from "../";
5
+
6
+ const MockComponent = () => {
7
+ const focusableContext = useContext(FocusableGroupContext) || "default";
8
+
9
+ return <Text testID={focusableContext} />;
10
+ };
11
+
12
+ describe("FocusableGroupContextProvider", () => {
13
+ it("should provide focusable context to the wrapped component", () => {
14
+ const { getByTestId } = render(
15
+ <FocusableGroupContextProvider id="testId">
16
+ <MockComponent />
17
+ </FocusableGroupContextProvider>
18
+ );
19
+
20
+ expect(getByTestId("testId")).toBeTruthy();
21
+ });
22
+ });
@@ -0,0 +1,26 @@
1
+ import React from "react";
2
+ import { Text } from "react-native";
3
+ import { render } from "@testing-library/react-native";
4
+ import { withFocusableContext } from "../withFocusableContext";
5
+ import { FocusableGroupContext } from "../FocusableGroupContext";
6
+
7
+ const MockComponent = ({ groupId }: { groupId: string }) => (
8
+ <Text testID={groupId} />
9
+ );
10
+
11
+ const WrappedComponent = withFocusableContext(MockComponent);
12
+
13
+ describe("withFocusableContext HOC", () => {
14
+ it("should provide focusable context to the wrapped component", () => {
15
+ const { getByTestId } = render(<WrappedComponent />, {
16
+ // eslint-disable-next-line react/display-name
17
+ wrapper: ({ children }: { children: React.ReactElement }) => (
18
+ <FocusableGroupContext.Provider value="test">
19
+ {children}
20
+ </FocusableGroupContext.Provider>
21
+ ),
22
+ });
23
+
24
+ expect(getByTestId("test")).toBeTruthy();
25
+ });
26
+ });
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+ import { FocusableGroupContext } from "./FocusableGroupContext";
3
+ import { FocusableGroupContextProvider } from "./FocusableGroupContextProvider";
4
+
5
+ export { FocusableGroupContext, FocusableGroupContextProvider };
6
+
7
+ export const useGroupId = () => React.useContext(FocusableGroupContext);
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+ import { FocusableGroupContext } from "./FocusableGroupContext";
3
+
4
+ export const withFocusableContext = (Component) => {
5
+ // eslint-disable-next-line react/display-name
6
+ return ({ groupId, ...props }: Record<string, any>) => (
7
+ <FocusableGroupContext.Consumer>
8
+ {(groupIdContext: string) => {
9
+ // eslint-disable-next-line react/display-name
10
+ const propsGroupId = groupId || null;
11
+ const providedGroupId = propsGroupId || groupIdContext;
12
+
13
+ return <Component {...props} groupId={providedGroupId} />;
14
+ }}
15
+ </FocusableGroupContext.Consumer>
16
+ );
17
+ };
@@ -2,7 +2,6 @@ import React, {
2
2
  createContext,
3
3
  ReactNode,
4
4
  useCallback,
5
- useEffect,
6
5
  useMemo,
7
6
  useRef,
8
7
  } from "react";
@@ -114,11 +113,6 @@ export function ScreenContextProvider({
114
113
  return navBarState;
115
114
  }, []);
116
115
 
117
- useEffect(() => {
118
- // purge the getScreenNavBarState store when the pathname changes
119
- screenNavBarStateRef.current = null;
120
- }, [pathname]);
121
-
122
116
  const screenNavBarState = getScreenNavBarState()(
123
117
  useShallow((state) => ({
124
118
  visible: state.visible,
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.5471145113",
3
+ "version": "12.0.0-alpha.5522474195",
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.5471145113",
38
- "@applicaster/zapp-react-native-bridge": "12.0.0-alpha.5471145113",
39
- "@applicaster/zapp-react-native-redux": "12.0.0-alpha.5471145113",
40
- "@applicaster/zapp-react-native-utils": "12.0.0-alpha.5471145113",
37
+ "@applicaster/applicaster-types": "12.0.0-alpha.5522474195",
38
+ "@applicaster/zapp-react-native-bridge": "12.0.0-alpha.5522474195",
39
+ "@applicaster/zapp-react-native-redux": "12.0.0-alpha.5522474195",
40
+ "@applicaster/zapp-react-native-utils": "12.0.0-alpha.5522474195",
41
41
  "promise": "^8.3.0",
42
42
  "react-router-native": "^5.1.2",
43
43
  "url": "^0.11.0",