@applicaster/zapp-react-native-ui-components 15.0.0-alpha.4390247850 → 15.0.0-alpha.4413958104

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 (64) hide show
  1. package/Components/AnimatedInOut/index.tsx +69 -26
  2. package/Components/Cell/Cell.tsx +8 -3
  3. package/Components/Cell/TvOSCellComponent.tsx +21 -5
  4. package/Components/HandlePlayable/HandlePlayable.tsx +17 -65
  5. package/Components/HandlePlayable/const.ts +3 -0
  6. package/Components/HandlePlayable/utils.ts +74 -0
  7. package/Components/Layout/TV/LayoutBackground.tsx +5 -2
  8. package/Components/Layout/TV/ScreenContainer.tsx +2 -6
  9. package/Components/Layout/TV/index.tsx +3 -4
  10. package/Components/Layout/TV/index.web.tsx +3 -4
  11. package/Components/LinkHandler/LinkHandler.tsx +2 -2
  12. package/Components/MasterCell/DefaultComponents/BorderContainerView/__tests__/index.test.tsx +16 -1
  13. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +30 -2
  14. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +10 -6
  15. package/Components/MasterCell/DefaultComponents/Text/index.tsx +8 -8
  16. package/Components/MasterCell/index.tsx +2 -0
  17. package/Components/MasterCell/utils/__tests__/resolveColor.test.js +82 -3
  18. package/Components/MasterCell/utils/index.ts +61 -31
  19. package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +102 -87
  20. package/Components/MeasurmentsPortal/__tests__/MeasurementsPortal.test.tsx +355 -0
  21. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +2 -2
  22. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +17 -18
  23. package/Components/OfflineHandler/__tests__/index.test.tsx +27 -18
  24. package/Components/PlayerContainer/PlayerContainer.tsx +5 -19
  25. package/Components/PlayerImageBackground/index.tsx +3 -22
  26. package/Components/Screen/TV/index.web.tsx +4 -2
  27. package/Components/Screen/__tests__/Screen.test.tsx +65 -42
  28. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +68 -44
  29. package/Components/Screen/hooks.ts +2 -3
  30. package/Components/Screen/index.tsx +2 -3
  31. package/Components/Screen/navigationHandler.ts +49 -24
  32. package/Components/Screen/orientationHandler.ts +3 -3
  33. package/Components/ScreenResolver/index.tsx +13 -7
  34. package/Components/ScreenRevealManager/ScreenRevealManager.ts +40 -8
  35. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +86 -69
  36. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +44 -26
  37. package/Components/Tabs/TV/Tabs.tsx +20 -3
  38. package/Components/Transitioner/Scene.tsx +15 -2
  39. package/Components/Transitioner/index.js +3 -3
  40. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +1 -0
  41. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +118 -171
  42. package/Components/VideoModal/ModalAnimation/index.ts +2 -13
  43. package/Components/VideoModal/ModalAnimation/utils.ts +1 -327
  44. package/Components/VideoModal/PlayerWrapper.tsx +14 -88
  45. package/Components/VideoModal/VideoModal.tsx +1 -5
  46. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -0
  47. package/Components/VideoModal/hooks/useModalSize.ts +10 -5
  48. package/Components/VideoModal/playerWrapperStyle.ts +70 -0
  49. package/Components/VideoModal/playerWrapperUtils.ts +91 -0
  50. package/Components/VideoModal/utils.ts +19 -9
  51. package/Decorators/Analytics/index.tsx +6 -5
  52. package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
  53. package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +80 -0
  54. package/Helpers/DataSourceHelper/index.ts +10 -0
  55. package/index.d.ts +7 -0
  56. package/package.json +6 -5
  57. package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +0 -60
  58. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +0 -417
  59. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.web.tsx +0 -294
  60. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +0 -176
  61. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.web.tsx +0 -93
  62. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +0 -500
  63. package/Components/VideoModal/ModalAnimation/__tests__/getMoveUpValue.test.ts +0 -108
  64. package/Helpers/DataSourceHelper/index.js +0 -19
@@ -6,10 +6,6 @@ import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
6
6
 
7
7
  type AnimatedInterpolatedStyle = any;
8
8
 
9
- // type AnimatedInterpolatedStyle =
10
- // | Animated.AnimatedInterpolation
11
- // | [{ [Key: string]: Animated.AnimatedInterpolation }];
12
-
13
9
  type AnimationConfig = {
14
10
  duration: number;
15
11
  easing: EasingFunction;
@@ -45,32 +41,57 @@ export function AnimatedInOut({
45
41
  children,
46
42
  }: Props) {
47
43
  const [animatedValue] = React.useState(new Animated.Value(visible ? 1 : 0));
48
- const [animating, setAnimating] = React.useState(undefined);
44
+ const animationRef = React.useRef<Animated.CompositeAnimation | null>(null);
45
+ const delayTimerRef = React.useRef<NodeJS.Timeout | null>(null);
49
46
 
50
47
  const previousVisible = usePrevious(toBooleanWithDefaultFalse(visible));
51
48
 
52
- function startAnimation(toValue, config) {
53
- if (animating) {
54
- animating.reset();
55
- }
56
-
57
- const { duration, easing, delay = 0, onAnimationEnd = noop } = config;
49
+ const startAnimation = React.useCallback(
50
+ (toValue: number, config: AnimationConfig) => {
51
+ if (delayTimerRef.current) {
52
+ clearTimeout(delayTimerRef.current);
53
+ delayTimerRef.current = null;
54
+ }
55
+
56
+ if (animationRef.current) {
57
+ animationRef.current.stop();
58
+ animationRef.current = null;
59
+ }
60
+
61
+ const { duration, easing, delay = 0, onAnimationEnd = noop } = config;
62
+
63
+ const runAnimation = () => {
64
+ animationRef.current = Animated.timing(animatedValue, {
65
+ duration,
66
+ toValue,
67
+ easing,
68
+ useNativeDriver: true,
69
+ });
70
+
71
+ animationRef.current.start(({ finished }) => {
72
+ if (finished) {
73
+ animationRef.current = null;
74
+ onAnimationEnd();
75
+ }
76
+ });
77
+ };
78
+
79
+ if (delay > 0) {
80
+ delayTimerRef.current = setTimeout(runAnimation, delay);
81
+ } else {
82
+ runAnimation();
83
+ }
84
+ },
85
+ [animatedValue]
86
+ );
58
87
 
59
- const compositeAnimation = Animated.timing(animatedValue, {
60
- duration,
61
- toValue,
62
- easing,
63
- delay,
64
- useNativeDriver: true,
65
- }).start(() => {
66
- setAnimating(undefined);
67
- onAnimationEnd();
68
- });
88
+ React.useEffect(() => {
89
+ if (previousVisible === undefined) {
90
+ animatedValue.setValue(visible ? 1 : 0);
69
91
 
70
- setAnimating(compositeAnimation);
71
- }
92
+ return;
93
+ }
72
94
 
73
- React.useEffect(() => {
74
95
  if (!previousVisible && visible) {
75
96
  startAnimation(1.0, getAnimation(animationConfig, "in"));
76
97
  }
@@ -78,7 +99,29 @@ export function AnimatedInOut({
78
99
  if (previousVisible && !visible) {
79
100
  startAnimation(0.0, getAnimation(animationConfig, "out"));
80
101
  }
81
- }, [visible, previousVisible]);
102
+ }, [
103
+ visible,
104
+ previousVisible,
105
+ animatedValue,
106
+ startAnimation,
107
+ animationConfig,
108
+ ]);
109
+
110
+ React.useEffect(() => {
111
+ return () => {
112
+ if (delayTimerRef.current) {
113
+ clearTimeout(delayTimerRef.current);
114
+ delayTimerRef.current = null;
115
+ }
116
+
117
+ if (animationRef.current) {
118
+ animationRef.current.stop();
119
+ animationRef.current = null;
120
+ }
121
+
122
+ animatedValue.stopAnimation();
123
+ };
124
+ }, [animatedValue]);
82
125
 
83
126
  const styles = visible
84
127
  ? getAnimation(animationConfig, "in").styles
@@ -86,7 +129,7 @@ export function AnimatedInOut({
86
129
 
87
130
  return (
88
131
  <Animated.View
89
- renderToHardwareTextureAndroid={animating}
132
+ renderToHardwareTextureAndroid={!!animationRef.current}
90
133
  style={[styles(animatedValue), staticStyles]}
91
134
  >
92
135
  {children}
@@ -208,14 +208,14 @@ export class CellComponent extends React.Component<Props, State> {
208
208
  this.accessibilityManager.readText({
209
209
  text: " ",
210
210
  });
211
- } else {
211
+ } else if (this.state.cellFocused) {
212
212
  this.accessibilityManager.readText({
213
213
  text: `${positionLabel}`,
214
214
  });
215
215
  }
216
216
  }
217
217
 
218
- componentDidUpdate(prevProps: Readonly<Props>) {
218
+ componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<State>) {
219
219
  if (prevProps.item !== this.props.item) {
220
220
  this.setState({
221
221
  hasFocusableInside: this.props.CellRenderer.hasFocusableInside?.(
@@ -224,7 +224,12 @@ export class CellComponent extends React.Component<Props, State> {
224
224
  });
225
225
  }
226
226
 
227
- this.handleAccessibilityFocus(this.props.index, this.props.dataLength);
227
+ if (
228
+ prevState.cellFocused !== this.state.cellFocused ||
229
+ this.state.hasFocusableInside
230
+ ) {
231
+ this.handleAccessibilityFocus(this.props.index, this.props.dataLength);
232
+ }
228
233
  }
229
234
 
230
235
  render() {
@@ -17,6 +17,7 @@ import { CellWithFocusable } from "./CellWithFocusable";
17
17
  import { FocusableWrapper } from "./FocusableWrapper";
18
18
 
19
19
  import { focusableButtonsRegistration$ } from "@applicaster/zapp-react-native-utils/appUtils/focusManagerAux/utils/utils.ios";
20
+ import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
20
21
 
21
22
  type Props = {
22
23
  item: ZappEntry;
@@ -37,6 +38,10 @@ type Props = {
37
38
  component: {
38
39
  id: number | string;
39
40
  component_type: string;
41
+ styles?: {
42
+ component_margin_top?: number;
43
+ component_padding_top?: number;
44
+ };
40
45
  };
41
46
  selected: boolean;
42
47
  CellRenderer: React.FunctionComponent<any> & {
@@ -201,14 +206,25 @@ class TvOSCell extends React.Component<Props, State> {
201
206
  ) {
202
207
  const { headerOffset } = getHeaderOffset();
203
208
 
204
- const extraAnchorPointYOffset =
205
- screenLayout?.extraAnchorPointYOffset || 0;
209
+ const extraAnchorPointYOffset = toNumberWithDefaultZero(
210
+ screenLayout?.extraAnchorPointYOffset
211
+ );
212
+
213
+ const componentMarginTop = toNumberWithDefaultZero(
214
+ component?.styles?.component_margin_top
215
+ );
216
+
217
+ const componentPaddingTop = toNumberWithDefaultZero(
218
+ component?.styles?.component_padding_top
219
+ );
206
220
 
207
221
  const totalOffset =
208
222
  headerOffset +
209
- (componentAnchorPointY || 0) +
210
- extraAnchorPointYOffset -
211
- componentsMapOffset || 0;
223
+ toNumberWithDefaultZero(componentAnchorPointY) +
224
+ extraAnchorPointYOffset -
225
+ toNumberWithDefaultZero(componentsMapOffset) +
226
+ componentMarginTop +
227
+ componentPaddingTop;
212
228
 
213
229
  mainOffsetUpdater?.(
214
230
  { tag: this.target },
@@ -1,14 +1,14 @@
1
1
  import * as React from "react";
2
- import * as R from "ramda";
3
2
  import {
4
- findPluginByType,
5
- findPluginByIdentifier,
6
- } from "@applicaster/zapp-react-native-utils/pluginUtils";
7
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
3
+ useAppData,
4
+ useContentTypes,
5
+ usePlugins,
6
+ } from "@applicaster/zapp-react-native-redux/hooks";
8
7
  import {
9
8
  useDimensions,
10
9
  useIsTablet as isTablet,
11
10
  useNavigation,
11
+ useRivers,
12
12
  } from "@applicaster/zapp-react-native-utils/reactHooks";
13
13
 
14
14
  import { BufferAnimation } from "../PlayerContainer/BufferAnimation";
@@ -16,6 +16,7 @@ import { PlayerContainer } from "../PlayerContainer";
16
16
  import { useModalSize } from "../VideoModal/hooks";
17
17
  import { ViewStyle } from "react-native";
18
18
  import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
19
+ import { findCastPlugin, getPlayer } from "./utils";
19
20
 
20
21
  type Props = {
21
22
  item: ZappEntry;
@@ -26,62 +27,6 @@ type Props = {
26
27
  groupId?: string;
27
28
  };
28
29
 
29
- const YOUTUBE_PLUGIN_ID = "youtube-player-qb";
30
- const CHROMECAST_PLUGIN_ID = "chromecast_qb";
31
-
32
- const getPlayerWithModuleProperties = (
33
- PlayerModule: ZappPlugin
34
- ): [ZappPlugin, PlayerModuleProperties] => {
35
- const getPlayerModuleProperties = R.ifElse(
36
- R.is(Object) && R.has("Component"),
37
- R.omit(["Component"]),
38
- () => ({})
39
- );
40
-
41
- return [
42
- PlayerModule?.Component || PlayerModule,
43
- getPlayerModuleProperties(PlayerModule),
44
- ];
45
- };
46
-
47
- const getPlayer = (
48
- item: ZappEntry,
49
- state
50
- ): [ZappPlugin, PlayerModuleProperties] => {
51
- const {
52
- plugins,
53
- contentTypes,
54
- rivers,
55
- appData: { layoutVersion },
56
- } = state;
57
-
58
- let PlayerModule;
59
-
60
- if (layoutVersion === "v2") {
61
- const { screen_id } = contentTypes?.[item?.type?.value] || {};
62
- const { type } = rivers?.[screen_id] || {};
63
-
64
- if (type) {
65
- PlayerModule = findPluginByIdentifier(type, plugins)?.module;
66
-
67
- return getPlayerWithModuleProperties(PlayerModule);
68
- }
69
- }
70
-
71
- if (item?.content?.type === "youtube-id") {
72
- PlayerModule = findPluginByIdentifier(YOUTUBE_PLUGIN_ID, plugins)?.module;
73
-
74
- return getPlayerWithModuleProperties(PlayerModule);
75
- }
76
-
77
- PlayerModule = findPluginByType(
78
- "playable",
79
- plugins.filter(({ identifier }) => identifier !== YOUTUBE_PLUGIN_ID)
80
- );
81
-
82
- return getPlayerWithModuleProperties(PlayerModule);
83
- };
84
-
85
30
  type PlayableComponent = {
86
31
  Component: React.ComponentType<any>;
87
32
  };
@@ -99,14 +44,21 @@ export function HandlePlayable({
99
44
  mode,
100
45
  groupId,
101
46
  }: Props): React.ReactElement | null {
102
- const state = usePickFromState();
47
+ const plugins = usePlugins();
48
+ const contentTypes = useContentTypes();
49
+ const rivers = useRivers();
50
+ const appData = useAppData();
103
51
 
104
52
  const { closeVideoModal } = useNavigation();
105
53
 
106
- const [Player, playerModuleProperties] = getPlayer(item, state);
54
+ const [Player, playerModuleProperties] = getPlayer(item, {
55
+ plugins,
56
+ contentTypes,
57
+ rivers,
58
+ appData,
59
+ });
107
60
 
108
- const { module: CastPlugin } =
109
- findPluginByIdentifier(CHROMECAST_PLUGIN_ID, state.plugins, true) || {};
61
+ const { module: CastPlugin } = findCastPlugin(plugins);
110
62
 
111
63
  const [playable, setPlayable] =
112
64
  React.useState<Nullable<PlayableComponent>>(null);
@@ -0,0 +1,3 @@
1
+ export const YOUTUBE_PLUGIN_ID = "youtube-player-qb";
2
+
3
+ export const CHROMECAST_PLUGIN_ID = "chromecast_qb";
@@ -0,0 +1,74 @@
1
+ import {
2
+ findPluginByIdentifier,
3
+ findPluginByType,
4
+ } from "@applicaster/zapp-react-native-utils/pluginUtils";
5
+
6
+ import { CHROMECAST_PLUGIN_ID, YOUTUBE_PLUGIN_ID } from "./const";
7
+ import { omit } from "@applicaster/zapp-react-native-utils/utils";
8
+
9
+ const getPlayerModuleProperties = (PlayerModule: ZappPlugin) => {
10
+ if (PlayerModule?.Component && typeof PlayerModule.Component === "object") {
11
+ return omit(["Component"], PlayerModule);
12
+ }
13
+
14
+ return {};
15
+ };
16
+
17
+ export const getPlayerWithModuleProperties = (
18
+ PlayerModule: ZappPlugin
19
+ ): [ZappPlugin, PlayerModuleProperties] => {
20
+ return [
21
+ PlayerModule?.Component || PlayerModule,
22
+ getPlayerModuleProperties(PlayerModule),
23
+ ];
24
+ };
25
+
26
+ export const findCastPlugin = (plugins: ZappPlugin[]) =>
27
+ findPluginByIdentifier(CHROMECAST_PLUGIN_ID, plugins, true) || {};
28
+
29
+ export const findYoutubePlugin = (plugins: ZappPlugin[]) =>
30
+ findPluginByIdentifier(YOUTUBE_PLUGIN_ID, plugins, true) || {};
31
+
32
+ export const getPlayer = (
33
+ item: ZappEntry,
34
+ {
35
+ plugins,
36
+ contentTypes,
37
+ rivers,
38
+ appData: { layoutVersion },
39
+ }: {
40
+ plugins: ZappPlugin[];
41
+ contentTypes: Record<string, any>;
42
+ rivers: Record<string, any>;
43
+ appData: { layoutVersion: string };
44
+ }
45
+ ): [ZappPlugin, PlayerModuleProperties] => {
46
+ let PlayerModule;
47
+
48
+ if (layoutVersion === "v2") {
49
+ const screen_id = contentTypes?.[item?.type?.value]?.screen_id;
50
+ const type = rivers?.[screen_id]?.type;
51
+
52
+ if (type) {
53
+ PlayerModule = findPluginByIdentifier(type, plugins)?.module;
54
+
55
+ return getPlayerWithModuleProperties(PlayerModule);
56
+ }
57
+ }
58
+
59
+ if (item?.content?.type === "youtube-id") {
60
+ PlayerModule = findYoutubePlugin(plugins)?.module;
61
+
62
+ return getPlayerWithModuleProperties(PlayerModule);
63
+ }
64
+
65
+ PlayerModule = findPluginByType(
66
+ "playable",
67
+ (plugins as any[]).filter(
68
+ ({ identifier }: { identifier: string }) =>
69
+ identifier !== YOUTUBE_PLUGIN_ID
70
+ )
71
+ );
72
+
73
+ return getPlayerWithModuleProperties(PlayerModule);
74
+ };
@@ -1,7 +1,10 @@
1
1
  import React from "react";
2
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
3
2
  import { getBackgroundImageUrl } from "../utils";
4
3
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
4
+ import {
5
+ selectRemoteConfigurations,
6
+ useAppSelector,
7
+ } from "@applicaster/zapp-react-native-redux";
5
8
 
6
9
  export const LayoutBackground = ({
7
10
  Background,
@@ -12,7 +15,7 @@ export const LayoutBackground = ({
12
15
  }) => {
13
16
  const theme = useTheme();
14
17
 
15
- const { remoteConfigurations } = usePickFromState(["remoteConfigurations"]);
18
+ const remoteConfigurations = useAppSelector(selectRemoteConfigurations);
16
19
 
17
20
  const backgroundColor = theme.app_background_color;
18
21
  const backgroundImageUrl = getBackgroundImageUrl(remoteConfigurations);
@@ -18,7 +18,7 @@ import {
18
18
  routeIsPlayerScreen,
19
19
  } from "@applicaster/zapp-react-native-utils/navigationUtils";
20
20
  import { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
21
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
21
+ import { usePlugins } from "@applicaster/zapp-react-native-redux/hooks";
22
22
  import { NavBarContainer } from "./NavBarContainer";
23
23
 
24
24
  type ComponentsExtraProps = {
@@ -111,11 +111,7 @@ export const ScreenContainer = React.memo(function ScreenContainer({
111
111
  const { activeRiver } = navigator;
112
112
  const { title, visible } = useNavbarState();
113
113
 
114
- const { plugins = [] } = usePickFromState([
115
- "appState",
116
- "remoteConfigurations",
117
- "plugins",
118
- ]);
114
+ const plugins = usePlugins();
119
115
 
120
116
  const navigationProps = getNavigationProps({
121
117
  navigator,
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
2
+ import { useAppSelector } from "@applicaster/zapp-react-native-redux/hooks";
3
3
  import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
4
4
 
5
5
  import { LayoutContainer } from "./LayoutContainer";
@@ -10,6 +10,7 @@ import { PathnameContext } from "../../../Contexts/PathnameContext";
10
10
  import { ScreenDataContext } from "../../../Contexts/ScreenDataContext";
11
11
  import { ScreenContextProvider } from "../../../Contexts/ScreenContext";
12
12
  import { LayoutBackground } from "./LayoutBackground";
13
+ import { selectAppReady } from "@applicaster/zapp-react-native-redux";
13
14
 
14
15
  type Components = {
15
16
  NavBar: React.ComponentType<any>;
@@ -29,9 +30,7 @@ type Props = {
29
30
  const Layout = ({ Components, ComponentsExtraProps, children }: Props) => {
30
31
  const navigator = useNavigation();
31
32
 
32
- const { appState: { appReady = false } = {} } = usePickFromState([
33
- "appState",
34
- ]);
33
+ const appReady = useAppSelector(selectAppReady);
35
34
 
36
35
  if (!appReady) {
37
36
  return null;
@@ -1,11 +1,11 @@
1
1
  import * as React from "react";
2
- import { pathOr } from "ramda";
3
2
 
4
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
3
+ import { useAppSelector } from "@applicaster/zapp-react-native-redux/hooks";
5
4
 
6
5
  import { ScreenLayoutContextProvider } from "./ScreenLayoutContextProvider";
7
6
  import { StackNavigator } from "../../Navigator";
8
7
  import { LayoutBackground } from "./LayoutBackground";
8
+ import { selectAppReady } from "@applicaster/zapp-react-native-redux";
9
9
 
10
10
  type Components = {
11
11
  NavBar: React.ComponentType<any>;
@@ -17,8 +17,7 @@ type Props = {
17
17
  };
18
18
 
19
19
  const Layout = ({ Components }: Props) => {
20
- const { appState } = usePickFromState(["appState"]);
21
- const appReady = pathOr(false, ["appReady"], appState);
20
+ const appReady = useAppSelector(selectAppReady);
22
21
 
23
22
  if (!appReady) {
24
23
  return null;
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
  import * as R from "ramda";
3
3
 
4
4
  import { findPluginByIdentifier } from "@applicaster/zapp-react-native-utils/pluginUtils";
5
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
5
+ import { usePlugins } from "@applicaster/zapp-react-native-redux/hooks";
6
6
  import {
7
7
  inflateString,
8
8
  objectToReadableString,
@@ -40,7 +40,7 @@ export async function inflateUrl(url) {
40
40
 
41
41
  export function LinkHandler(props: Props) {
42
42
  const screenData = props?.screenData;
43
- const { plugins } = usePickFromState(["rivers", "plugins"]);
43
+ const plugins = usePlugins();
44
44
 
45
45
  const ScreenPlugin = findPluginByIdentifier(
46
46
  WEBVIEW_SCREEN_IDENTIFIER,
@@ -1,8 +1,8 @@
1
+ import * as React from "react";
1
2
  import {
2
3
  BorderContainerView,
3
4
  getBorderPadding, // Export for testing (using a double underscore prefix is a common convention)
4
5
  } from "../index";
5
- import * as React from "react";
6
6
  import { render } from "@testing-library/react-native";
7
7
  import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
8
8
  import { View } from "react-native";
@@ -11,6 +11,15 @@ jest.mock("@applicaster/zapp-react-native-utils/numberUtils", () => ({
11
11
  toNumberWithDefaultZero: jest.fn((value) => Number(value) || 0),
12
12
  }));
13
13
 
14
+ jest.mock(
15
+ "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager/hooks",
16
+ () => ({
17
+ useAccessibilityManager: jest.fn(() => ({
18
+ addHeading: jest.fn(),
19
+ })),
20
+ })
21
+ );
22
+
14
23
  describe("BorderContainerView", () => {
15
24
  describe("getBorderPadding", () => {
16
25
  it("returns 0 for inside", () => {
@@ -42,6 +51,8 @@ describe("BorderContainerView", () => {
42
51
  };
43
52
 
44
53
  const borderPosition = null;
54
+ const mockEntry = { id: "test-entry" } as ZappEntry;
55
+ const mockHasFocusableInside = jest.fn(() => false);
45
56
 
46
57
  const { queryByTestId } = render(
47
58
  <BorderContainerView
@@ -52,6 +63,10 @@ describe("BorderContainerView", () => {
52
63
  borderPaddingRight={toNumberWithDefaultZero(padding.paddingRight)}
53
64
  borderPaddingBottom={toNumberWithDefaultZero(padding.paddingBottom)}
54
65
  borderPaddingLeft={toNumberWithDefaultZero(padding.paddingLeft)}
66
+ hasFocusableInside={mockHasFocusableInside}
67
+ entry={mockEntry}
68
+ state="focused"
69
+ hasTextLabels={false}
55
70
  >
56
71
  <View testID="child" />
57
72
  </BorderContainerView>
@@ -1,10 +1,16 @@
1
- import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
2
- import * as React from "react";
1
+ import React, { useMemo, useContext, useEffect } from "react";
3
2
  import { ImageStyle, StyleSheet, View, ViewStyle } from "react-native";
3
+ import { useAccessibilityManager } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager/hooks";
4
+ import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
5
+ import { MeasurementPortalContext } from "../../../MeasurmentsPortal/MeasurementsPortal";
4
6
 
5
7
  type BorderPosition = "inside" | "outside" | "center";
6
8
 
7
9
  interface Props {
10
+ hasFocusableInside: (entry: ZappEntry) => boolean;
11
+ entry: ZappEntry;
12
+ state: CellState;
13
+ hasTextLabels: boolean;
8
14
  style: ImageStyle | ViewStyle;
9
15
  borderPosition: BorderPosition;
10
16
  borderPaddingTop: number;
@@ -118,8 +124,30 @@ export const BorderContainerView = (props: Props) => {
118
124
  borderPaddingLeft,
119
125
  style,
120
126
  children,
127
+ hasFocusableInside,
128
+ entry,
129
+ state,
130
+ hasTextLabels,
121
131
  } = props;
122
132
 
133
+ const accessibilityManager = useAccessibilityManager();
134
+ const isMeasurement = useContext(MeasurementPortalContext);
135
+
136
+ const isImageOnlyCell = useMemo(
137
+ () =>
138
+ state === "focused" &&
139
+ !hasTextLabels &&
140
+ !isMeasurement?.measuringInProgress &&
141
+ !hasFocusableInside(entry),
142
+ [entry, hasTextLabels, state, isMeasurement?.measuringInProgress]
143
+ );
144
+
145
+ useEffect(() => {
146
+ if (isImageOnlyCell && entry?.title) {
147
+ accessibilityManager.addHeading(String(entry.title));
148
+ }
149
+ }, [isImageOnlyCell, entry?.title]);
150
+
123
151
  const padding =
124
152
  borderPosition === "outside"
125
153
  ? {
@@ -9,6 +9,7 @@ import { useTrackCurrentAutoScrollingElement } from "@applicaster/zapp-react-nat
9
9
  import { useUIComponentContext } from "@applicaster/zapp-react-native-ui-components/Contexts/UIComponentContext";
10
10
  import { getPropComponentType } from "@applicaster/zapp-react-native-utils/cellUtils";
11
11
  import { findPluginByIdentifier } from "@applicaster/zapp-react-native-utils/pluginUtils";
12
+ import { useAccessibilityState } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager/hooks";
12
13
 
13
14
  type LiveImageProps = {
14
15
  item: ZappEntry;
@@ -108,8 +109,7 @@ const prepareEntry = (entry) => {
108
109
  };
109
110
  }
110
111
 
111
- const previewPlayback =
112
- entry.extensions?.["brightcove"]?.["preview_playback"];
112
+ const previewPlayback = entry.extensions?.brightcove?.preview_playback;
113
113
 
114
114
  if (previewPlayback) {
115
115
  return {
@@ -117,14 +117,14 @@ const prepareEntry = (entry) => {
117
117
  extensions: {
118
118
  ...entry.extensions,
119
119
  brightcove: {
120
- ...entry?.extensions?.["brightcove"],
120
+ ...entry?.extensions?.brightcove,
121
121
  video_id: previewPlayback,
122
122
  },
123
123
  },
124
124
  };
125
125
  }
126
126
 
127
- if (entry.extensions?.["brightcove"]?.["video_id"]) {
127
+ if (entry.extensions?.brightcove?.video_id) {
128
128
  return entry;
129
129
  }
130
130
 
@@ -174,7 +174,7 @@ const getPlayerConfig = (player_screen_id, actionIdentifier) => {
174
174
  // TODO: Add more dict if needed from the screen component, styles, data etc
175
175
  return {
176
176
  playerPluginId: playerScreen?.type ?? DEFAULT_PLAYER_IDENTIFIER,
177
- screenConfig: playerScreen?.["general"],
177
+ screenConfig: playerScreen?.general,
178
178
  };
179
179
  }
180
180
 
@@ -206,6 +206,9 @@ const LiveImageComponent = (props: LiveImageProps) => {
206
206
  state,
207
207
  } = props;
208
208
 
209
+ const accessibilityState = useAccessibilityState({});
210
+ const isScreenReaderEnabled = accessibilityState.screenReaderEnabled;
211
+
209
212
  const component = useUIComponentContext();
210
213
 
211
214
  // Fix for blinking on state change
@@ -239,7 +242,8 @@ const LiveImageComponent = (props: LiveImageProps) => {
239
242
  getFocusedState(state, componentType, isCurrentlyFocused) &&
240
243
  playableEntry &&
241
244
  cellUUID &&
242
- isSupportedTVForLiveImage();
245
+ isSupportedTVForLiveImage() &&
246
+ !isScreenReaderEnabled;
243
247
 
244
248
  return (
245
249
  <>