@applicaster/zapp-react-native-ui-components 15.0.0-alpha.3512356987 → 15.0.0-alpha.3564377339

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 (47) hide show
  1. package/Components/BaseFocusable/index.ios.ts +12 -2
  2. package/Components/Cell/FocusableWrapper.tsx +3 -0
  3. package/Components/Cell/TvOSCellComponent.tsx +17 -5
  4. package/Components/Focusable/FocusableTvOS.tsx +1 -0
  5. package/Components/FocusableGroup/FocusableTvOS.tsx +2 -0
  6. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +8 -1
  7. package/Components/HandlePlayable/HandlePlayable.tsx +10 -7
  8. package/Components/Layout/TV/LayoutBackground.tsx +5 -2
  9. package/Components/Layout/TV/ScreenContainer.tsx +2 -6
  10. package/Components/Layout/TV/index.tsx +3 -4
  11. package/Components/Layout/TV/index.web.tsx +3 -4
  12. package/Components/LinkHandler/LinkHandler.tsx +2 -2
  13. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +4 -4
  14. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +5 -1
  15. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +11 -3
  16. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +9 -1
  17. package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +15 -14
  18. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +10 -6
  19. package/Components/MasterCell/utils/__tests__/resolveColor.test.js +82 -3
  20. package/Components/MasterCell/utils/index.ts +61 -31
  21. package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +102 -87
  22. package/Components/MeasurmentsPortal/__tests__/MeasurementsPortal.test.tsx +355 -0
  23. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +2 -2
  24. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +17 -18
  25. package/Components/OfflineHandler/__tests__/index.test.tsx +27 -18
  26. package/Components/PlayerContainer/PlayerContainer.tsx +4 -3
  27. package/Components/Screen/TV/index.web.tsx +4 -2
  28. package/Components/Screen/__tests__/Screen.test.tsx +65 -42
  29. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +68 -44
  30. package/Components/Screen/hooks.ts +2 -3
  31. package/Components/Screen/index.tsx +2 -3
  32. package/Components/Screen/navigationHandler.ts +49 -24
  33. package/Components/Screen/orientationHandler.ts +3 -3
  34. package/Components/ScreenResolver/index.tsx +13 -7
  35. package/Components/ScreenRevealManager/ScreenRevealManager.ts +40 -8
  36. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +86 -69
  37. package/Components/Transitioner/Scene.tsx +15 -2
  38. package/Components/Transitioner/index.js +3 -3
  39. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +13 -9
  40. package/Components/VideoModal/utils.ts +12 -9
  41. package/Decorators/Analytics/index.tsx +6 -5
  42. package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
  43. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
  44. package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +80 -0
  45. package/Helpers/DataSourceHelper/index.ts +19 -0
  46. package/package.json +6 -5
  47. package/Helpers/DataSourceHelper/index.js +0 -19
@@ -22,6 +22,7 @@ type Props = {
22
22
  onFocus?: FocusManager.FocusEventCB;
23
23
  onBlur?: FocusManager.FocusEventCB;
24
24
  selected?: boolean;
25
+ skipFocusManagerRegistration?: boolean;
25
26
  };
26
27
 
27
28
  export class BaseFocusable<
@@ -61,10 +62,14 @@ export class BaseFocusable<
61
62
  }
62
63
 
63
64
  componentDidMount() {
64
- const { id } = this.props;
65
+ const { id, skipFocusManagerRegistration } = this.props;
65
66
  const component = this;
66
67
  this.node = this.ref.current;
67
68
 
69
+ if (skipFocusManagerRegistration) {
70
+ return;
71
+ }
72
+
68
73
  focusManager.register({
69
74
  id,
70
75
  component: component,
@@ -118,7 +123,12 @@ export class BaseFocusable<
118
123
 
119
124
  componentWillUnmount() {
120
125
  this._isMounted = false;
121
- const { id } = this.props;
126
+ const { id, skipFocusManagerRegistration } = this.props;
127
+
128
+ if (skipFocusManagerRegistration) {
129
+ return;
130
+ }
131
+
122
132
  focusManager.unregister(id, { group: this.isGroup || false });
123
133
  }
124
134
 
@@ -10,6 +10,7 @@ type Props = {
10
10
  children: (focused: boolean) => React.ReactNode;
11
11
  onFocus: (arg1: any, index?: number) => void;
12
12
  onBlur: Callback;
13
+ skipFocusManagerRegistration?: boolean;
13
14
  };
14
15
 
15
16
  export const FocusableWrapper = ({
@@ -20,6 +21,7 @@ export const FocusableWrapper = ({
20
21
  applyWrapper,
21
22
  onFocus,
22
23
  onBlur,
24
+ skipFocusManagerRegistration,
23
25
  }: Props) => {
24
26
  if (applyWrapper) {
25
27
  return (
@@ -34,6 +36,7 @@ export const FocusableWrapper = ({
34
36
  // @ts-ignore
35
37
  offsetUpdater={noop}
36
38
  isFocusable
39
+ skipFocusManagerRegistration={skipFocusManagerRegistration}
37
40
  >
38
41
  {(focused) => children(focused)}
39
42
  </Focusable>
@@ -40,6 +40,7 @@ type Props = {
40
40
  component_type: string;
41
41
  styles?: {
42
42
  component_margin_top?: number;
43
+ component_padding_top?: number;
43
44
  };
44
45
  };
45
46
  selected: boolean;
@@ -79,6 +80,7 @@ type Props = {
79
80
  componentsMapOffset: number;
80
81
  applyFocusableWrapper: boolean;
81
82
  hasFocusableInside: boolean;
83
+ skipFocusManagerRegistration?: boolean;
82
84
  };
83
85
 
84
86
  type State = {
@@ -205,19 +207,25 @@ class TvOSCell extends React.Component<Props, State> {
205
207
  ) {
206
208
  const { headerOffset } = getHeaderOffset();
207
209
 
208
- const extraAnchorPointYOffset =
209
- screenLayout?.extraAnchorPointYOffset || 0;
210
+ const extraAnchorPointYOffset = toNumberWithDefaultZero(
211
+ screenLayout?.extraAnchorPointYOffset
212
+ );
210
213
 
211
214
  const componentMarginTop = toNumberWithDefaultZero(
212
215
  component?.styles?.component_margin_top
213
216
  );
214
217
 
218
+ const componentPaddingTop = toNumberWithDefaultZero(
219
+ component?.styles?.component_padding_top
220
+ );
221
+
215
222
  const totalOffset =
216
223
  headerOffset +
217
- (componentAnchorPointY || 0) +
224
+ toNumberWithDefaultZero(componentAnchorPointY) +
218
225
  extraAnchorPointYOffset -
219
- (componentsMapOffset || 0) +
220
- componentMarginTop;
226
+ toNumberWithDefaultZero(componentsMapOffset) +
227
+ componentMarginTop +
228
+ componentPaddingTop;
221
229
 
222
230
  mainOffsetUpdater?.(
223
231
  { tag: this.target },
@@ -259,6 +267,7 @@ class TvOSCell extends React.Component<Props, State> {
259
267
  behavior,
260
268
  applyFocusableWrapper,
261
269
  hasFocusableInside,
270
+ skipFocusManagerRegistration,
262
271
  } = this.props;
263
272
 
264
273
  const { id } = item;
@@ -284,6 +293,7 @@ class TvOSCell extends React.Component<Props, State> {
284
293
  onFocus={handleFocus}
285
294
  onBlur={onBlur || this.onBlur}
286
295
  applyWrapper={applyFocusableWrapper}
296
+ skipFocusManagerRegistration={skipFocusManagerRegistration}
287
297
  >
288
298
  {(focused) => (
289
299
  <CellWithFocusable
@@ -298,6 +308,7 @@ class TvOSCell extends React.Component<Props, State> {
298
308
  focused={focused || this.props.focused}
299
309
  behavior={behavior}
300
310
  isFocusable={isFocusable}
311
+ skipFocusManagerRegistration={skipFocusManagerRegistration}
301
312
  />
302
313
  )}
303
314
  </FocusableWrapper>
@@ -320,6 +331,7 @@ class TvOSCell extends React.Component<Props, State> {
320
331
  offsetUpdater={offsetUpdater}
321
332
  style={baseCellStyles}
322
333
  isFocusable={isFocusable}
334
+ skipFocusManagerRegistration={skipFocusManagerRegistration}
323
335
  >
324
336
  {(focused) => (
325
337
  <FocusableCell
@@ -39,6 +39,7 @@ type Props = {
39
39
  hasReceivedFocus: () => void;
40
40
  offsetUpdater: (arg1: string, arg2: number) => number;
41
41
  style: ViewStyle;
42
+ skipFocusManagerRegistration?: boolean;
42
43
  };
43
44
 
44
45
  export class Focusable extends BaseFocusable<Props> {
@@ -34,6 +34,8 @@ type Props = {
34
34
  };
35
35
 
36
36
  export class FocusableGroup extends BaseFocusable<Props> {
37
+ public readonly isGroup: boolean = true;
38
+
37
39
  render() {
38
40
  const {
39
41
  children,
@@ -35,6 +35,7 @@ type Feeds = Record<string, ZappPipesData>;
35
35
 
36
36
  type LayoutPresets = PresetsMapping["presets_mappings"];
37
37
 
38
+ const TABS_SCREEN_TYPE = "tabs_screen";
38
39
  const SMART_COMPONENT_TYPE = "quick-brick-smart-component";
39
40
  const SOURCE_PATH = ["data", "source"];
40
41
  const MAPPING_PATH = ["data", "mapping"];
@@ -131,10 +132,16 @@ export const useCurationAPI = (
131
132
  );
132
133
 
133
134
  const { pathname } = useRoute();
134
- const [entryContext] = ZappPipesEntryContext.useZappPipesContext(pathname);
135
135
  const [searchContext] = ZappPipesSearchContext.useZappPipesContext();
136
136
  const [screenContext] = ZappPipesScreenContext.useZappPipesContext();
137
137
 
138
+ const isNestedScreen = screenContext?.type === TABS_SCREEN_TYPE;
139
+
140
+ const [entryContext] = ZappPipesEntryContext.useZappPipesContext(
141
+ pathname,
142
+ isNestedScreen
143
+ );
144
+
138
145
  const urlsMap = useMemo<{ [key: string]: string }>(() => {
139
146
  const map = {};
140
147
 
@@ -1,9 +1,14 @@
1
1
  import * as React from "react";
2
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
2
+ import {
3
+ useAppData,
4
+ useContentTypes,
5
+ usePlugins,
6
+ } from "@applicaster/zapp-react-native-redux/hooks";
3
7
  import {
4
8
  useDimensions,
5
9
  useIsTablet as isTablet,
6
10
  useNavigation,
11
+ useRivers,
7
12
  } from "@applicaster/zapp-react-native-utils/reactHooks";
8
13
 
9
14
  import { BufferAnimation } from "../PlayerContainer/BufferAnimation";
@@ -39,12 +44,10 @@ export function HandlePlayable({
39
44
  mode,
40
45
  groupId,
41
46
  }: Props): React.ReactElement | null {
42
- const { plugins, contentTypes, rivers, appData } = usePickFromState([
43
- "plugins",
44
- "contentTypes",
45
- "rivers",
46
- "appData",
47
- ]);
47
+ const plugins = usePlugins();
48
+ const contentTypes = useContentTypes();
49
+ const rivers = useRivers();
50
+ const appData = useAppData();
48
51
 
49
52
  const { closeVideoModal } = useNavigation();
50
53
 
@@ -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,
@@ -135,11 +135,11 @@ export const BorderContainerView = (props: Props) => {
135
135
 
136
136
  const isImageOnlyCell = useMemo(
137
137
  () =>
138
- !hasFocusableInside(entry) &&
139
- !hasTextLabels &&
140
138
  state === "focused" &&
141
- !isMeasurement,
142
- [hasFocusableInside, entry, hasTextLabels, state, isMeasurement]
139
+ !hasTextLabels &&
140
+ !isMeasurement?.measuringInProgress &&
141
+ !hasFocusableInside(entry),
142
+ [entry, hasTextLabels, state, isMeasurement?.measuringInProgress]
143
143
  );
144
144
 
145
145
  useEffect(() => {
@@ -32,6 +32,7 @@ export default function Image({
32
32
  placeholderImage,
33
33
  entry,
34
34
  withDimensions,
35
+ source: sourceProp,
35
36
  ...otherProps
36
37
  }: Props) {
37
38
  const [showDefault, setShowDefault] = React.useState(false);
@@ -48,7 +49,10 @@ export default function Image({
48
49
  entry,
49
50
  showDefault,
50
51
  placeholderImage: placeholderImage || "",
51
- otherProps,
52
+ otherProps: {
53
+ source: sourceProp,
54
+ state: otherProps.state,
55
+ },
52
56
  });
53
57
 
54
58
  const onError = React.useCallback(() => {
@@ -1,8 +1,8 @@
1
1
  import * as React from "react";
2
2
  import { Image as RnImage, ImageStyle } from "react-native";
3
- import { equals, omit } from "ramda";
4
3
 
5
4
  import { useImageSource } from "./hooks";
5
+ import { equals } from "@applicaster/zapp-react-native-utils/utils";
6
6
 
7
7
  type Source = {
8
8
  uri: string;
@@ -25,11 +25,19 @@ function Image({
25
25
  placeholderImage,
26
26
  entry,
27
27
  withDimensions,
28
+ source: sourceProp,
28
29
  ...otherProps
29
30
  }: Props) {
30
31
  const [error, setErrorState] = React.useState(null);
31
32
 
32
- const source = useImageSource({ uri, entry, otherProps });
33
+ const source = useImageSource({
34
+ uri,
35
+ entry,
36
+ otherProps: {
37
+ source: sourceProp,
38
+ state: otherProps.state,
39
+ },
40
+ });
33
41
 
34
42
  React.useEffect(() => {
35
43
  // reset error state on URI change as the error is referencing previous uri
@@ -49,7 +57,7 @@ function Image({
49
57
  onError={React.useCallback(() => setErrorState(true), [])}
50
58
  // as we have defaults as "" for placeholder image, we need to pass undefined to source to not throw warnings
51
59
  source={_source?.uri ? _source : undefined}
52
- {...omit(["source"], otherProps)}
60
+ {...otherProps}
53
61
  />
54
62
  );
55
63
  }
@@ -23,9 +23,17 @@ function Image({
23
23
  placeholderImage,
24
24
  entry,
25
25
  withDimensions,
26
+ source: sourceProp,
26
27
  ...otherProps
27
28
  }: Props) {
28
- const source = useImageSource({ uri, entry, otherProps });
29
+ const source = useImageSource({
30
+ uri,
31
+ entry,
32
+ otherProps: {
33
+ source: sourceProp,
34
+ state: otherProps.state,
35
+ },
36
+ });
29
37
 
30
38
  const updatedSource = source ? withDimensions(source) : { uri: "" };
31
39
 
@@ -1,19 +1,21 @@
1
1
  import * as React from "react";
2
- import { path } from "ramda";
3
2
 
4
3
  import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
5
4
  import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
6
5
  import { extractAsset } from "./utils";
7
6
 
8
7
  type Return = { uri: string } | null;
8
+ type Source = { context?: string; uri?: string } | null | undefined;
9
9
 
10
- const getSourceContext = path(["source", "context"]);
11
- const getSourceUri = path(["source", "uri"]);
12
- const getState = path(["state"]);
10
+ const getSourceContext = (source: Source) => source?.context;
11
+ const getSourceUri = (source: Source) => source?.uri;
13
12
 
14
- export const useImageSource = ({ uri, entry, otherProps }): Return => {
15
- const uriContext = getSourceContext(otherProps);
16
- const uriState = getState(otherProps);
13
+ export const useImageSource = ({
14
+ uri,
15
+ entry,
16
+ otherProps: { source, state: uriState },
17
+ }): Return => {
18
+ const uriContext = getSourceContext(source);
17
19
 
18
20
  const action = useActions(uriContext);
19
21
 
@@ -38,7 +40,7 @@ export const useImageSource = ({ uri, entry, otherProps }): Return => {
38
40
  return { uri };
39
41
  }
40
42
 
41
- const uriFromSource = getSourceUri(otherProps);
43
+ const uriFromSource = getSourceUri(source);
42
44
 
43
45
  if (uriFromSource) {
44
46
  return { uri: uriFromSource };
@@ -47,7 +49,7 @@ export const useImageSource = ({ uri, entry, otherProps }): Return => {
47
49
  return null;
48
50
  };
49
51
 
50
- const getSource = (uri, showDefault, placeholderImage, otherProps) => {
52
+ const getSource = (uri, showDefault, placeholderImage, source) => {
51
53
  const placeholderName = placeholderImage || "";
52
54
 
53
55
  const defaultPath = {
@@ -60,7 +62,7 @@ const getSource = (uri, showDefault, placeholderImage, otherProps) => {
60
62
  return { uri };
61
63
  }
62
64
 
63
- const uriFromSource = getSourceUri(otherProps);
65
+ const uriFromSource = getSourceUri(source);
64
66
 
65
67
  if (uriFromSource) {
66
68
  return { uri: uriFromSource };
@@ -74,10 +76,9 @@ export const useImageSourceWithDefault = ({
74
76
  entry,
75
77
  showDefault,
76
78
  placeholderImage,
77
- otherProps,
79
+ otherProps: { state: uriState, source },
78
80
  }): Return => {
79
- const uriContext = getSourceContext(otherProps);
80
- const uriState = getState(otherProps);
81
+ const uriContext = getSourceContext(source);
81
82
 
82
83
  const action = useActions(uriContext);
83
84
 
@@ -98,5 +99,5 @@ export const useImageSourceWithDefault = ({
98
99
  return extractAsset(!isTV(), entryStateLocal.asset, uriState);
99
100
  }
100
101
 
101
- return getSource(uri, showDefault, placeholderImage, otherProps);
102
+ return getSource(uri, showDefault, placeholderImage, source);
102
103
  };
@@ -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
  <>
@@ -32,14 +32,16 @@ describe("resolveColor", () => {
32
32
  color: "invalid_path",
33
33
  };
34
34
 
35
- expect(resolveColor(entry, style)).toEqual(style);
35
+ expect(resolveColor(entry, style)).toEqual({
36
+ color: undefined,
37
+ });
36
38
 
37
39
  expect(loggerSpy).toHaveBeenCalledWith(
38
40
  expect.objectContaining({
39
41
  message: "Cannot resolve property invalid_path from the entry.",
40
42
  data: {
43
+ colorFromProp: "invalid_path",
41
44
  configurationValue: "invalid_path",
42
- entry,
43
45
  },
44
46
  })
45
47
  );
@@ -102,7 +104,9 @@ describe("resolveColor", () => {
102
104
  color: "not.exist.path",
103
105
  };
104
106
 
105
- expect(resolveColor(entry, style)).toEqual(style);
107
+ expect(resolveColor(entry, style)).toEqual({
108
+ color: undefined,
109
+ });
106
110
  });
107
111
 
108
112
  it("not modify style with empty path", () => {
@@ -112,4 +116,79 @@ describe("resolveColor", () => {
112
116
 
113
117
  expect(resolveColor(entry, style)).toEqual(style);
114
118
  });
119
+
120
+ describe("memoization", () => {
121
+ beforeEach(() => {
122
+ // Clear memoization cache before each test
123
+ resolveColor.clear && resolveColor.clear();
124
+ });
125
+
126
+ it("hits cache with same entry and style references", () => {
127
+ const style = { color: "extensions.color" };
128
+
129
+ const result1 = resolveColor(entry, style);
130
+ const result2 = resolveColor(entry, style);
131
+
132
+ expect(result1).toBe(result2); // Same object reference
133
+ });
134
+
135
+ it("hits cache with new references but equal entry/style values", () => {
136
+ const entryClone = {
137
+ extensions: {
138
+ color: "red",
139
+ green_color: "green",
140
+ },
141
+ };
142
+
143
+ const style = { color: "extensions.color" };
144
+ const styleClone = { color: "extensions.color" };
145
+
146
+ const result1 = resolveColor(entry, style);
147
+ const result2 = resolveColor(entryClone, styleClone);
148
+
149
+ expect(result1).toBe(result2);
150
+ });
151
+
152
+ it("misses cache when entry is new object", () => {
153
+ const entry2 = { extensions: { color: "blue" } }; // Same values, different object
154
+ const style = { color: "extensions.color" };
155
+
156
+ const result1 = resolveColor(entry, style);
157
+ const result2 = resolveColor(entry2, style);
158
+
159
+ expect(result1).not.toBe(result2); // Different object references
160
+ });
161
+
162
+ it("misses cache when entry property changes", () => {
163
+ const myEntry = {
164
+ extensions: {
165
+ color: "red",
166
+ green_color: "green",
167
+ },
168
+ };
169
+
170
+ const style = { color: "extensions.color" };
171
+
172
+ const result1 = resolveColor(myEntry, style);
173
+
174
+ myEntry.extensions.color = "blue"; // Change property
175
+ const result2 = resolveColor(myEntry, style);
176
+
177
+ expect(result1).toEqual({ color: "red" });
178
+ expect(result2).toEqual({ color: "blue" });
179
+ expect(result1).not.toBe(result2);
180
+ });
181
+
182
+ it("misses cache when style changes", () => {
183
+ const style1 = { color: "extensions.color" };
184
+ const style2 = { backgroundColor: "extensions.color" };
185
+
186
+ const result1 = resolveColor(entry, style1);
187
+ const result2 = resolveColor(entry, style2);
188
+
189
+ expect(result1).toEqual({ color: "red" });
190
+ expect(result2).toEqual({ backgroundColor: "red" });
191
+ expect(result1).not.toBe(result2);
192
+ });
193
+ });
115
194
  });