@applicaster/zapp-react-native-ui-components 15.0.0-rc.13 → 15.0.0-rc.131

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 (189) hide show
  1. package/Components/AnimatedInOut/index.tsx +69 -26
  2. package/Components/BaseFocusable/index.ios.ts +12 -2
  3. package/Components/Cell/Cell.tsx +14 -3
  4. package/Components/Cell/CellWithFocusable.tsx +9 -0
  5. package/Components/Cell/FocusableWrapper.tsx +3 -0
  6. package/Components/Cell/TvOSCellComponent.tsx +30 -6
  7. package/Components/Focusable/Focusable.tsx +4 -2
  8. package/Components/Focusable/FocusableTvOS.tsx +18 -1
  9. package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +1 -0
  10. package/Components/FocusableGroup/FocusableTvOS.tsx +32 -1
  11. package/Components/GeneralContentScreen/GeneralContentScreen.tsx +39 -28
  12. package/Components/GeneralContentScreen/__tests__/GeneralContentScreen.test.tsx +104 -0
  13. package/Components/GeneralContentScreen/utils/__tests__/getScreenDataSource.test.ts +19 -0
  14. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
  15. package/Components/GeneralContentScreen/utils/getScreenDataSource.ts +9 -0
  16. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +22 -6
  17. package/Components/HandlePlayable/HandlePlayable.tsx +33 -94
  18. package/Components/HandlePlayable/const.ts +3 -0
  19. package/Components/HandlePlayable/utils.ts +105 -0
  20. package/Components/HookRenderer/HookRenderer.tsx +40 -10
  21. package/Components/HookRenderer/__tests__/HookRenderer.test.tsx +60 -0
  22. package/Components/Layout/TV/LayoutBackground.tsx +5 -2
  23. package/Components/Layout/TV/NavBarContainer.tsx +1 -10
  24. package/Components/Layout/TV/ScreenContainer.tsx +2 -6
  25. package/Components/Layout/TV/__tests__/__snapshots__/NavBarContainer.test.tsx.snap +7 -12
  26. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +7 -12
  27. package/Components/Layout/TV/index.tsx +3 -4
  28. package/Components/Layout/TV/index.web.tsx +3 -4
  29. package/Components/LinkHandler/LinkHandler.tsx +2 -2
  30. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/model.test.ts +80 -0
  31. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +187 -0
  32. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/selectors.test.ts +45 -0
  33. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/style.test.ts +49 -0
  34. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/model.ts +47 -0
  35. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +170 -0
  36. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/selectors.ts +26 -0
  37. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/style.ts +29 -0
  38. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/types.ts +37 -0
  39. package/Components/MasterCell/DefaultComponents/BorderContainerView/__tests__/index.test.tsx +16 -1
  40. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +30 -2
  41. package/Components/MasterCell/DefaultComponents/Button.tsx +0 -15
  42. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +5 -1
  43. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +11 -3
  44. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +9 -1
  45. package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +15 -14
  46. package/Components/MasterCell/DefaultComponents/LiveImage/__tests__/prepareEntry.test.ts +352 -0
  47. package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +136 -0
  48. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +43 -22
  49. package/Components/MasterCell/DefaultComponents/PressableView.tsx +196 -0
  50. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +40 -39
  51. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/Image.test.tsx +95 -0
  52. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +86 -0
  53. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/index.test.ts +141 -0
  54. package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +7 -6
  55. package/Components/MasterCell/DefaultComponents/SecondaryImage/index.ts +1 -1
  56. package/Components/MasterCell/DefaultComponents/Text/index.tsx +10 -14
  57. package/Components/MasterCell/DefaultComponents/index.ts +2 -0
  58. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +46 -0
  59. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +126 -0
  60. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ButtonContainerView.ts +23 -0
  61. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +16 -0
  62. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabel.ts +67 -0
  63. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +32 -0
  64. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +191 -0
  65. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +140 -0
  66. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +222 -0
  67. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/helpers.ts +105 -0
  68. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +104 -0
  69. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +118 -0
  70. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +73 -0
  71. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +86 -0
  72. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +35 -48
  73. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +115 -29
  74. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +39 -144
  75. package/Components/MasterCell/elementMapper.tsx +1 -0
  76. package/Components/MasterCell/hoc/__tests__/withAsyncRender.test.tsx +219 -0
  77. package/Components/MasterCell/hoc/withAsyncRender.tsx +9 -7
  78. package/Components/MasterCell/index.tsx +2 -0
  79. package/Components/MasterCell/utils/__tests__/resolveColor.test.js +82 -3
  80. package/Components/MasterCell/utils/index.ts +61 -31
  81. package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +102 -87
  82. package/Components/MeasurmentsPortal/__tests__/MeasurementsPortal.test.tsx +355 -0
  83. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +17 -9
  84. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +16 -8
  85. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +2 -2
  86. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +17 -18
  87. package/Components/OfflineHandler/NotificationView/utils.ts +34 -0
  88. package/Components/OfflineHandler/__tests__/index.test.tsx +27 -18
  89. package/Components/PlayerContainer/PlayerContainer.tsx +43 -64
  90. package/Components/PlayerImageBackground/index.tsx +3 -22
  91. package/Components/PreloaderWrapper/__tests__/index.test.tsx +26 -0
  92. package/Components/PreloaderWrapper/index.tsx +15 -0
  93. package/Components/River/ComponentsMap/ComponentsMap.tsx +16 -0
  94. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
  95. package/Components/River/RefreshControl.tsx +36 -13
  96. package/Components/River/RiverItem.tsx +26 -20
  97. package/Components/River/TV/River.tsx +31 -14
  98. package/Components/River/TV/index.tsx +8 -4
  99. package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +30 -0
  100. package/Components/River/TV/utils/index.ts +4 -0
  101. package/Components/River/TV/withFocusableGroupForContent.tsx +71 -0
  102. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +2 -0
  103. package/Components/River/__tests__/componentsMap.test.js +38 -0
  104. package/Components/Screen/TV/index.web.tsx +4 -2
  105. package/Components/Screen/__tests__/Screen.test.tsx +66 -42
  106. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +68 -44
  107. package/Components/Screen/hooks.ts +75 -6
  108. package/Components/Screen/index.tsx +9 -4
  109. package/Components/Screen/navigationHandler.ts +49 -24
  110. package/Components/Screen/orientationHandler.ts +10 -13
  111. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +46 -0
  112. package/Components/ScreenFeedLoader/__tests__/ScreenFeedLoader.test.tsx +94 -0
  113. package/Components/ScreenFeedLoader/index.ts +1 -0
  114. package/Components/ScreenResolver/__tests__/screenResolver.test.js +24 -0
  115. package/Components/ScreenResolver/hooks/index.ts +3 -0
  116. package/Components/ScreenResolver/hooks/useGetComponent.ts +15 -0
  117. package/Components/ScreenResolver/hooks/useScreenComponentResolver.tsx +90 -0
  118. package/Components/ScreenResolver/index.tsx +15 -111
  119. package/Components/ScreenResolver/utils/__tests__/getScreenTypeProps.test.ts +45 -0
  120. package/Components/ScreenResolver/utils/getScreenTypeProps.ts +43 -0
  121. package/Components/ScreenResolver/utils/index.ts +1 -0
  122. package/Components/ScreenResolver/withDefaultScreenContext.tsx +16 -0
  123. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +25 -0
  124. package/Components/ScreenResolverFeedProvider/__tests__/ScreenResolverFeedProvider.test.tsx +44 -0
  125. package/Components/ScreenResolverFeedProvider/index.ts +1 -0
  126. package/Components/ScreenRevealManager/ScreenRevealManager.ts +40 -8
  127. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +86 -69
  128. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +44 -26
  129. package/Components/Tabs/TV/Tabs.tsx +20 -3
  130. package/Components/Tabs/TabContent.tsx +7 -4
  131. package/Components/Transitioner/Scene.tsx +10 -3
  132. package/Components/Transitioner/index.js +3 -3
  133. package/Components/VideoLive/LiveImageManager.ts +199 -54
  134. package/Components/VideoLive/PlayerLiveImageComponent.tsx +31 -33
  135. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +2 -17
  136. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +1 -0
  137. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +118 -171
  138. package/Components/VideoModal/ModalAnimation/index.ts +2 -13
  139. package/Components/VideoModal/ModalAnimation/utils.ts +1 -327
  140. package/Components/VideoModal/PlayerWrapper.tsx +14 -88
  141. package/Components/VideoModal/VideoModal.tsx +1 -5
  142. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -0
  143. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +15 -7
  144. package/Components/VideoModal/hooks/useModalSize.ts +10 -5
  145. package/Components/VideoModal/playerWrapperStyle.ts +70 -0
  146. package/Components/VideoModal/playerWrapperUtils.ts +91 -0
  147. package/Components/VideoModal/utils.ts +19 -9
  148. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
  149. package/Components/Viewport/ViewportAware/index.tsx +16 -7
  150. package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
  151. package/Components/ZappUIComponent/index.tsx +12 -6
  152. package/Components/default-cell-renderer/viewTrees/mobile/index.ts +0 -3
  153. package/Components/index.js +1 -1
  154. package/Contexts/ScreenContext/__tests__/index.test.tsx +57 -0
  155. package/Contexts/ScreenContext/index.tsx +71 -19
  156. package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
  157. package/Contexts/ZappHookModalContext/index.tsx +37 -61
  158. package/Contexts/ZappPipesContext/ZappPipesContextFactory.tsx +18 -7
  159. package/Contexts/index.ts +0 -2
  160. package/Decorators/Analytics/index.tsx +6 -5
  161. package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +1 -0
  162. package/Decorators/ConfigurationWrapper/const.ts +1 -0
  163. package/Decorators/ZappPipesDataConnector/ResolverSelector.tsx +25 -7
  164. package/Decorators/ZappPipesDataConnector/__tests__/ResolverSelector.test.tsx +212 -5
  165. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
  166. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
  167. package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
  168. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
  169. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
  170. package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +80 -0
  171. package/Helpers/DataSourceHelper/index.ts +19 -0
  172. package/events/index.ts +3 -0
  173. package/events/scrollEndReached.ts +15 -0
  174. package/index.d.ts +7 -0
  175. package/package.json +6 -5
  176. package/Components/MasterCell/DefaultComponents/Text/utils/__tests__/withAdjustedLineHeight.test.ts +0 -46
  177. package/Components/MasterCell/DefaultComponents/Text/utils/index.ts +0 -21
  178. package/Components/PlayerContainer/ErrorDisplay/ErrorDisplay.tsx +0 -57
  179. package/Components/PlayerContainer/ErrorDisplay/index.ts +0 -9
  180. package/Components/River/TV/withTVEventHandler.tsx +0 -27
  181. package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +0 -60
  182. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +0 -417
  183. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.web.tsx +0 -294
  184. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +0 -176
  185. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.web.tsx +0 -93
  186. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +0 -500
  187. package/Components/VideoModal/ModalAnimation/__tests__/getMoveUpValue.test.ts +0 -108
  188. package/Helpers/DataSourceHelper/index.js +0 -19
  189. /package/Components/HookRenderer/{index.tsx → index.ts} +0 -0
@@ -1,8 +1,8 @@
1
1
  import * as React from "react";
2
- import { Animated } from "react-native";
2
+ import { Animated, StyleSheet } from "react-native";
3
3
  import { isFirstComponentScreenPicker } from "@applicaster/zapp-react-native-utils/componentsUtils";
4
- import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
5
4
  import { useRefWithInitialValue } from "@applicaster/zapp-react-native-utils/reactHooks/state/useRefWithInitialValue";
5
+ import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
6
6
 
7
7
  import { ScreenRevealManager } from "./ScreenRevealManager";
8
8
  import {
@@ -10,16 +10,14 @@ import {
10
10
  emitScreenRevealManagerIsNotReadyToShow,
11
11
  } from "./utils";
12
12
 
13
- const flex = platformSelect({
14
- tvos: 1,
15
- android_tv: 1,
16
- web: undefined,
17
- samsung_tv: undefined,
18
- lg_tv: undefined,
19
- default: undefined,
13
+ const styles = StyleSheet.create({
14
+ container: {
15
+ ...StyleSheet.absoluteFillObject,
16
+ position: "absolute",
17
+ },
20
18
  });
21
19
 
22
- export const TIMEOUT = 500; // 500 ms
20
+ export const TIMEOUT = 300; // 300 ms
23
21
 
24
22
  const HIDDEN = 0; // opacity = 0
25
23
 
@@ -33,37 +31,48 @@ export const withScreenRevealManager = (Component) => {
33
31
  return function WithScreenRevealManager(props: Props) {
34
32
  const { componentsToRender } = props;
35
33
 
36
- const [isReadyToShow, setIsReadyToShow] = React.useState(false);
34
+ const [isContentReadyToBeShown, setIsContentReadyToBeShown] =
35
+ React.useState(false);
37
36
 
38
- const handleSetIsReadyToShow = React.useCallback(() => {
39
- setIsReadyToShow(true);
37
+ const [isShowOverlay, setIsShowOverlay] = React.useState(true);
38
+
39
+ const theme = useTheme<BaseThemePropertiesTV>();
40
+
41
+ const handleSetIsContentReadyToBeShown = React.useCallback(() => {
42
+ setIsContentReadyToBeShown(true);
40
43
  }, []);
41
44
 
42
45
  const managerRef = useRefWithInitialValue<ScreenRevealManager>(
43
- () => new ScreenRevealManager(componentsToRender, handleSetIsReadyToShow)
46
+ () =>
47
+ new ScreenRevealManager(
48
+ componentsToRender,
49
+ handleSetIsContentReadyToBeShown
50
+ )
44
51
  );
45
52
 
46
53
  const opacityRef = useRefWithInitialValue<Animated.Value>(
47
- () => new Animated.Value(HIDDEN)
54
+ () => new Animated.Value(SHOWN)
48
55
  );
49
56
 
50
57
  React.useEffect(() => {
51
- if (!isReadyToShow) {
58
+ if (!isContentReadyToBeShown) {
52
59
  emitScreenRevealManagerIsNotReadyToShow();
53
60
  } else {
54
61
  emitScreenRevealManagerIsReadyToShow();
55
62
  }
56
- }, [isReadyToShow]);
63
+ }, [isContentReadyToBeShown]);
57
64
 
58
65
  React.useEffect(() => {
59
- if (isReadyToShow) {
66
+ if (isContentReadyToBeShown) {
60
67
  Animated.timing(opacityRef.current, {
61
- toValue: SHOWN,
68
+ toValue: HIDDEN,
62
69
  duration: TIMEOUT,
63
70
  useNativeDriver: true,
64
- }).start();
71
+ }).start(() => {
72
+ setIsShowOverlay(false);
73
+ });
65
74
  }
66
- }, [isReadyToShow]);
75
+ }, [isContentReadyToBeShown]);
67
76
 
68
77
  if (isFirstComponentScreenPicker(componentsToRender)) {
69
78
  // for screen-picker with have additional internal ComponentsMap, no need to add this wrapper
@@ -71,10 +80,7 @@ export const withScreenRevealManager = (Component) => {
71
80
  }
72
81
 
73
82
  return (
74
- <Animated.View
75
- style={{ opacity: opacityRef.current, flex }}
76
- testID="animated-component"
77
- >
83
+ <>
78
84
  <Component
79
85
  {...props}
80
86
  initialNumberToLoad={
@@ -85,7 +91,19 @@ export const withScreenRevealManager = (Component) => {
85
91
  }
86
92
  onLoadFailedFromScreenRevealManager={managerRef.current.onLoadFailed}
87
93
  />
88
- </Animated.View>
94
+ {isShowOverlay ? (
95
+ <Animated.View
96
+ style={[
97
+ styles.container,
98
+ {
99
+ opacity: opacityRef.current,
100
+ backgroundColor: theme.app_background_color,
101
+ },
102
+ ]}
103
+ testID="animated-component"
104
+ />
105
+ ) : null}
106
+ </>
89
107
  );
90
108
  };
91
109
  };
@@ -8,6 +8,7 @@ import { isEmptyOrNil } from "@applicaster/zapp-react-native-utils/cellUtils";
8
8
  import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
9
9
  import { FocusableGroup } from "@applicaster/zapp-react-native-ui-components/Components/FocusableGroup";
10
10
  import { Focusable } from "@applicaster/zapp-react-native-ui-components/Components/Focusable";
11
+ import { useAccessibilityManager } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager/hooks";
11
12
  import { Gutter } from "../Gutter";
12
13
  import Tab from "./Tab";
13
14
  import { getStyles } from "./styles";
@@ -28,11 +29,14 @@ const TabsComponent = ({
28
29
  style,
29
30
  selectedEntryIndex,
30
31
  setSelectedEntry,
32
+ accessibility,
31
33
  }: TabsProps & Partial<TabsSelectionContextType>) => {
32
34
  const configuration = useConfiguration();
33
35
  const config = applyFontConfig(configuration);
34
36
  const styles = useMemo(() => getStyles(config), [config]);
35
37
 
38
+ const accessibilityManager = useAccessibilityManager({});
39
+
36
40
  const {
37
41
  tab_bar_gutter: horizontalGutter,
38
42
  tab_bar_background_image: bgImage,
@@ -60,10 +64,20 @@ const TabsComponent = ({
60
64
  );
61
65
 
62
66
  const onListElementFocus = useCallback(
63
- (index) => {
67
+ (index, isSelected: boolean, item: ZappEntry) => {
68
+ if (isSelected) {
69
+ accessibilityManager.readText({
70
+ text: `${accessibility?.selectedHint} ${item.title}`,
71
+ });
72
+ } else {
73
+ accessibilityManager.readText({
74
+ text: `${accessibility?.hint} ${item.title}`,
75
+ });
76
+ }
77
+
64
78
  scrollToSelectedIndex(index, VIEW_POSITION);
65
79
  },
66
- [scrollToSelectedIndex]
80
+ [scrollToSelectedIndex, accessibility]
67
81
  );
68
82
 
69
83
  const renderItem = useCallback(
@@ -94,7 +108,7 @@ const TabsComponent = ({
94
108
  id={itemId}
95
109
  testID={itemId}
96
110
  preferredFocus={isSelected}
97
- onFocus={() => onListElementFocus(index)}
111
+ onFocus={() => onListElementFocus(index, isSelected, item)}
98
112
  onPress={() => setSelectedEntry && setSelectedEntry(item)}
99
113
  style={style}
100
114
  >
@@ -149,6 +163,9 @@ const TabsComponent = ({
149
163
  shouldUsePreferredFocus
150
164
  isWithMemory={false}
151
165
  nextFocusDown={parentFocus?.nextFocusDown}
166
+ onFocus={() => {
167
+ accessibilityManager.addHeading(accessibility?.announcement);
168
+ }}
152
169
  >
153
170
  <View style={tabs}>
154
171
  <ImageBackground
@@ -1,11 +1,10 @@
1
1
  import React from "react";
2
- import { View, ViewStyle } from "react-native";
2
+ import { View, StyleSheet } from "react-native";
3
3
 
4
4
  import { River } from "@applicaster/zapp-react-native-ui-components/Components";
5
5
  import { withNestedNavigationContextProvider } from "@applicaster/zapp-react-native-ui-components/Contexts/NestedNavigationContext";
6
6
 
7
7
  type Props = {
8
- styles: Record<string, ViewStyle>;
9
8
  minHeight: number;
10
9
  changingTab: boolean;
11
10
  feedUrl: string;
@@ -14,9 +13,14 @@ type Props = {
14
13
  backgroundColor: string;
15
14
  };
16
15
 
16
+ const styles = StyleSheet.create({
17
+ riverWrapper: {
18
+ flex: 1,
19
+ },
20
+ });
21
+
17
22
  function TabContentComponent(props: Props) {
18
23
  const {
19
- styles,
20
24
  minHeight,
21
25
  backgroundColor,
22
26
  changingTab,
@@ -29,7 +33,6 @@ function TabContentComponent(props: Props) {
29
33
  <View
30
34
  style={[
31
35
  styles.riverWrapper,
32
-
33
36
  {
34
37
  backgroundColor,
35
38
  minHeight,
@@ -1,9 +1,9 @@
1
1
  import React from "react";
2
2
  import { equals } from "ramda";
3
3
  import { Animated, ViewProps, ViewStyle } from "react-native";
4
- import { useSafeAreaFrame } from "react-native-safe-area-context";
5
4
 
6
5
  import { useScreenOrientationHandler } from "@applicaster/zapp-react-native-ui-components/Components/Screen/orientationHandler";
6
+ import { useMemoizedSafeAreaFrameWithActiveState } from "@applicaster/zapp-react-native-ui-components/Components/Screen/hooks";
7
7
 
8
8
  import { PathnameContext } from "../../Contexts/PathnameContext";
9
9
  import { ScreenDataContext } from "../../Contexts/ScreenDataContext";
@@ -94,10 +94,17 @@ function SceneComponent({
94
94
  isActive,
95
95
  });
96
96
 
97
- const frame = useSafeAreaFrame();
97
+ // Use shared memoized frame hook - synchronized with useWaitForValidOrientation
98
+ // to prevent race conditions during orientation changes
99
+ // Pass isActive from props since Scene knows its active state from Transitioner
100
+ const memoFrame = useMemoizedSafeAreaFrameWithActiveState({
101
+ updateForInactiveScreens: false,
102
+ isActive,
103
+ });
104
+
98
105
  const isAnimating = animating && overlayStyle;
99
106
 
100
- const dimensions = isAnimating ? fullWidthDimensions : frame;
107
+ const dimensions = isAnimating ? fullWidthDimensions : memoFrame;
101
108
 
102
109
  return (
103
110
  <Animated.View
@@ -1,7 +1,8 @@
1
1
  import * as React from "react";
2
2
  import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
3
3
  import { useDimensions } from "@applicaster/zapp-react-native-utils/reactHooks/layout";
4
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
4
+ import { useAppData } from "@applicaster/zapp-react-native-redux";
5
+
5
6
  import { TransitionerComponent } from "./Transitioner";
6
7
 
7
8
  export const Transitioner = (props) => {
@@ -16,8 +17,7 @@ export const Transitioner = (props) => {
16
17
  deviceInfo: true,
17
18
  });
18
19
 
19
- const { appData } = usePickFromState(["appData"]);
20
- const isTabletPortrait = appData?.isTabletPortrait;
20
+ const { isTabletPortrait } = useAppData();
21
21
 
22
22
  return (
23
23
  <TransitionerComponent
@@ -4,9 +4,14 @@ import {
4
4
  playerManager,
5
5
  } from "@applicaster/zapp-react-native-utils/appUtils/playerManager";
6
6
  import { setUserCellPlayerMutedPreference } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/userCellPlayerMutedPreference";
7
+ import { playerFactory } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/playerFactory";
8
+ import { PlayerRole } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/conts";
7
9
  import { loggerLiveImageManager } from "./loggerHelper";
8
10
  import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
9
- import { Component } from "react";
11
+ import {
12
+ executePreloadHooks,
13
+ PreloadHookConfig,
14
+ } from "../MasterCell/DefaultComponents/LiveImage/executePreloadHooks";
10
15
 
11
16
  const TIMEOUT_FOR_DELAY_CHECK_PLAYER_POSITION = 500; // ms
12
17
 
@@ -40,13 +45,21 @@ type Position = {
40
45
  right: number;
41
46
  };
42
47
 
48
+ type PlayerFactoryConfig = {
49
+ player: any; // React ref for native view
50
+ playerId: string;
51
+ muted: boolean;
52
+ playerPluginId: string;
53
+ screenConfig: Record<string, any>;
54
+ entry: ZappEntry; // original entry, used as fallback if no hooks
55
+ };
56
+
43
57
  type LiveImageProps = {
44
- player: Player;
45
58
  playerId: string;
46
59
  setMode?: (type: LiveImageType) => void;
47
- component: Component;
48
- // TODO: ...primary, powerCell, tvGallery, etc.
49
- // type: string;
60
+ preloadHooks?: PreloadHookConfig[];
61
+ factoryConfig: PlayerFactoryConfig;
62
+ tag: string;
50
63
  };
51
64
 
52
65
  // Disabled because we have only unmute button but no play/pause state anymore
@@ -85,7 +98,7 @@ export class LiveImageManager implements PlayerLifecycleListener {
85
98
 
86
99
  public register = (item: LiveImage): (() => void) => {
87
100
  this.items.push(item);
88
- log_debug(`register: live image ${playerInfo(item.player)}`);
101
+ log_debug(`register: live image ${item.playerId} - ${item.tag}`);
89
102
 
90
103
  // TV only Start playing video once registered
91
104
  if (isTV()) {
@@ -96,15 +109,13 @@ export class LiveImageManager implements PlayerLifecycleListener {
96
109
  };
97
110
 
98
111
  public unregister = (item: LiveImage) => {
99
- log_debug(`unregister: live-image ${playerInfo(item.player)}`);
112
+ log_debug(`unregister: live-image ${item.playerId} - ${item.tag}`);
100
113
 
101
114
  if (this.currentlyPlaying === item) {
102
115
  this.currentlyPlaying = null;
103
116
 
104
117
  log_debug(
105
- `unregister: currently playing live-image was destroyed, ${playerInfo(
106
- item.player
107
- )}`
118
+ `unregister: currently playing live-image was destroyed, ${item.playerId} - ${item.tag}`
108
119
  );
109
120
 
110
121
  // TODO: Maybe start another one
@@ -118,7 +129,7 @@ export class LiveImageManager implements PlayerLifecycleListener {
118
129
  item,
119
130
  playerId: this.currentlyPlaying?.playerId,
120
131
  primaryPlayerId: this.primaryPlayer?.playerId,
121
- entry: item.getPlayer().getEntry(),
132
+ entry: item.getPlayer()?.getEntry(),
122
133
  },
123
134
  });
124
135
  };
@@ -130,9 +141,7 @@ export class LiveImageManager implements PlayerLifecycleListener {
130
141
 
131
142
  public onViewportEnter = (item: LiveImage) => {
132
143
  log_debug(
133
- `onViewportEnter: live-image ${playerInfo(
134
- item.player
135
- )}, primary ${playerInfo(this.primaryPlayer)}`
144
+ `onViewportEnter: live-image ${item.playerId} - ${item.tag}, primary ${playerInfo(this.primaryPlayer)} - position:${item.positionToString()}`
136
145
  );
137
146
 
138
147
  if (!isTV()) {
@@ -154,9 +163,7 @@ export class LiveImageManager implements PlayerLifecycleListener {
154
163
 
155
164
  public onViewportLeave = (item: LiveImage) => {
156
165
  log_debug(
157
- `onViewportLeave: live-image playerId: ${playerInfo(
158
- item.player
159
- )}, primary ${playerInfo(this.primaryPlayer)}`
166
+ `onViewportLeave: live-image ${item.playerId} - ${item.tag}, primary ${playerInfo(this.primaryPlayer)} - position:${item.positionToString()}`
160
167
  );
161
168
 
162
169
  this.pauseItem(item);
@@ -190,20 +197,29 @@ export class LiveImageManager implements PlayerLifecycleListener {
190
197
  this.items.find((i) => i.playerId === playerId) || null;
191
198
 
192
199
  private pauseItem = (item: LiveImage) => {
193
- log_debug(`pauseItem: live-image ${playerInfo(item.player)}`);
200
+ log_debug(`pauseItem: live-image ${item.playerId} - ${item.tag}`);
201
+
202
+ if (!item.player) {
203
+ // Player not yet created (e.g. hooks still running) — just reset mode
204
+ item.setMode?.(LiveImageType.Image);
205
+
206
+ if (item === this.currentlyPlaying) {
207
+ this.currentlyPlaying = null;
208
+ }
209
+
210
+ return;
211
+ }
194
212
 
195
213
  if (!item.player.playerState.isReadyToPlay) {
196
214
  log_debug(
197
- `playItem: live-image not ready, will start playback after loading, ${playerInfo(
198
- item.player
199
- )}`
215
+ `pauseItem: live-image not ready, ${item.playerId} - ${item.tag}`
200
216
  );
201
217
  } else {
202
- item.player?.pause();
218
+ item.player.pause();
203
219
  }
204
220
 
205
221
  // Fake close event, because we unmount native view
206
- item.player?.onPlayerClose();
222
+ item.player.onPlayerClose();
207
223
  item.setMode?.(LiveImageType.Image);
208
224
 
209
225
  if (item === this.currentlyPlaying) {
@@ -213,9 +229,7 @@ export class LiveImageManager implements PlayerLifecycleListener {
213
229
 
214
230
  public playLiveImage = (item: LiveImage) => {
215
231
  log_debug(
216
- `playLiveImage: live-image ${playerInfo(
217
- item.player
218
- )}, primary ${playerInfo(this.primaryPlayer)}`
232
+ `playLiveImage: live-image ${item.playerId} - ${item.tag}, primary ${playerInfo(this.primaryPlayer)}`
219
233
  );
220
234
 
221
235
  if (this.primaryPlayer) {
@@ -223,7 +237,7 @@ export class LiveImageManager implements PlayerLifecycleListener {
223
237
  }
224
238
 
225
239
  if (this.currentlyPlaying) {
226
- if (this.currentlyPlaying?.player?.playerId === item.player.playerId) {
240
+ if (this.currentlyPlaying.playerId === item.playerId) {
227
241
  return;
228
242
  } else {
229
243
  this.pauseItem(this.currentlyPlaying);
@@ -231,18 +245,42 @@ export class LiveImageManager implements PlayerLifecycleListener {
231
245
  }
232
246
 
233
247
  this.currentlyPlaying = item;
234
- item.setMode?.(LiveImageType.Video);
235
248
 
236
- if (item.player.playerState.isReadyToPlay) {
237
- item.player.play();
238
- }
249
+ item
250
+ .prepareForPlayback()
251
+ .then((result) => {
252
+ if (!result) {
253
+ log_error(
254
+ `Failed to prepare live image ${item.playerId} - ${item.tag} for playback: prepareForPlayback returned false`
255
+ );
256
+
257
+ this.currentlyPlaying = null;
258
+ item.setMode?.(LiveImageType.Image);
259
+
260
+ return;
261
+ }
262
+
263
+ // Guard: item might have been replaced while hooks were running
264
+ if (this.currentlyPlaying !== item) return;
265
+
266
+ item.setMode?.(LiveImageType.Video);
267
+
268
+ if (item.player?.playerState.isReadyToPlay) {
269
+ item.player.play();
270
+ }
271
+ })
272
+ .catch((error) => {
273
+ log_error(
274
+ `Failed to prepare live image ${item.playerId} - ${item.tag} for playback: ${error?.message}`
275
+ );
276
+
277
+ this.onLiveImageError(item, error);
278
+ });
239
279
  };
240
280
 
241
281
  public pauseLiveImage = (item: LiveImage) => {
242
282
  log_debug(
243
- `pauseLiveImage: live-image playerId: ${playerInfo(
244
- item.player
245
- )}, primary ${playerInfo(this.primaryPlayer)}`
283
+ `pauseLiveImage: live-image ${item.playerId} - ${item.tag}, primary ${playerInfo(this.primaryPlayer)}`
246
284
  );
247
285
 
248
286
  this.pauseItem(item);
@@ -258,7 +296,7 @@ export class LiveImageManager implements PlayerLifecycleListener {
258
296
 
259
297
  setUserCellPlayerMutedPreference(true);
260
298
 
261
- this.items.forEach((liveImage) => liveImage.player.mute());
299
+ this.items.forEach((liveImage) => liveImage.player?.mute());
262
300
  };
263
301
 
264
302
  public unmuteAll = () => {
@@ -266,16 +304,36 @@ export class LiveImageManager implements PlayerLifecycleListener {
266
304
 
267
305
  setUserCellPlayerMutedPreference(false);
268
306
 
269
- this.items.forEach((liveImage) => liveImage.player.unmute());
307
+ this.items.forEach((liveImage) => liveImage.player?.unmute());
270
308
  };
271
309
 
272
- public checkPlayerPosition = (item: LiveImage) => {
310
+ public onViewPositionChanged = (item: LiveImage) => {
311
+ log_debug(
312
+ `onViewPositionChanged: live-image ${item.playerId} - ${item.tag}, primary ${playerInfo(this.primaryPlayer)} - position:${item.positionToString()}`
313
+ );
314
+
315
+ if (!isTV()) {
316
+ // mobile only
317
+ // we have to delay running checkPlayerPosition, because sometimes on fast scrolling we get wrong order onEnter, then onLeave.
318
+ // which could cause select wrong item to play
319
+
320
+ this.cancelCheckPlayerPositionTimeout();
321
+
322
+ this.checkPlayerPositionTimeout = setTimeout(() => {
323
+ this.cancelCheckPlayerPositionTimeout();
324
+
325
+ this.checkPlayerPosition(item);
326
+ }, TIMEOUT_FOR_DELAY_CHECK_PLAYER_POSITION);
327
+ } else {
328
+ this.checkPlayerPosition(item);
329
+ }
330
+ };
331
+
332
+ private checkPlayerPosition = (item: LiveImage) => {
273
333
  this.cancelCheckPlayerPositionTimeout();
274
334
 
275
335
  log_debug(
276
- `checkPlayerPosition: live-image playerId: ${playerInfo(
277
- item.player
278
- )}, primary ${playerInfo(this.primaryPlayer)}`
336
+ `checkPlayerPosition: live-image ${item.playerId} - ${item.tag}, primary ${playerInfo(this.primaryPlayer)}`
279
337
  );
280
338
 
281
339
  const playerItem = this.findNextPlayableItem();
@@ -391,7 +449,7 @@ export class LiveImageManager implements PlayerLifecycleListener {
391
449
  item,
392
450
  playerId: this.currentlyPlaying?.playerId,
393
451
  primaryPlayerId: this.primaryPlayer?.playerId,
394
- entry: item.getPlayer().getEntry(),
452
+ entry: item.getPlayer()?.getEntry(),
395
453
  },
396
454
  });
397
455
  };
@@ -426,7 +484,7 @@ export class LiveImageManager implements PlayerLifecycleListener {
426
484
  item,
427
485
  playerId: this.currentlyPlaying?.playerId,
428
486
  primaryPlayerId: this.primaryPlayer?.playerId,
429
- entry: item.getPlayer().getEntry(),
487
+ entry: item.getPlayer()?.getEntry(),
430
488
  },
431
489
  });
432
490
  };
@@ -448,7 +506,7 @@ export class LiveImageManager implements PlayerLifecycleListener {
448
506
  this.currentlyPlaying = null;
449
507
 
450
508
  log_debug(
451
- `onLiveImageError: currentitem: ${currentItem.playerId} was removed`
509
+ `onLiveImageError: currentItem: ${currentItem.playerId} - ${currentItem.tag} was removed`
452
510
  );
453
511
 
454
512
  // TODO: ...Maybe player some other item
@@ -460,8 +518,8 @@ export class LiveImageManager implements PlayerLifecycleListener {
460
518
  item,
461
519
  error,
462
520
  playerId: currentItem?.playerId,
463
- primaryPlayerId: currentItem?.playerId,
464
- entry: item.getPlayer().getEntry(),
521
+ primaryPlayerId: this.primaryPlayer?.playerId,
522
+ entry: item.getPlayer()?.getEntry(),
465
523
  },
466
524
  });
467
525
  };
@@ -503,10 +561,10 @@ export class LiveImageManager implements PlayerLifecycleListener {
503
561
  LiveImageManager.instance;
504
562
 
505
563
  export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
506
- public player: Player;
507
- public setMode: (type: LiveImageType) => void;
508
- // Will be replaced with rects
564
+ public player: Player | null = null;
565
+ public setMode?: (type: LiveImageType) => void;
509
566
  public isFullyVisible: boolean = false;
567
+ public tag: string;
510
568
  public position: Position = {
511
569
  centerX: 0,
512
570
  centerY: 0,
@@ -516,18 +574,105 @@ export class LiveImage implements QuickBrickPlayer.SharedPlayerCallBacks {
516
574
  left: 0,
517
575
  };
518
576
 
577
+ positionToString() {
578
+ if (!this.position) {
579
+ return "position not set";
580
+ }
581
+
582
+ const { centerX, centerY, top, bottom, left, right } = this.position;
583
+
584
+ return `centerX: ${centerX.toFixed(2)}, centerY: ${centerY.toFixed(2)}, top: ${top.toFixed(2)}, bottom: ${bottom.toFixed(2)}, left: ${left.toFixed(2)}, right: ${right.toFixed(2)}`;
585
+ }
586
+
519
587
  readonly playerId: string;
520
- readonly component: Component;
588
+ public component: any = null;
589
+
590
+ private factoryConfig: PlayerFactoryConfig;
591
+ public preloadHooks?: PreloadHookConfig[];
592
+ public processedEntry: ZappEntry | null = null;
593
+ private _preparePromise: Promise<boolean> | null = null;
521
594
 
522
595
  constructor(props: LiveImageProps) {
523
- this.player = props.player;
524
596
  this.setMode = props.setMode;
525
- this.playerId = this.player.playerId;
526
- this.component = props.component;
527
- this.player.addListener({ id: "live-image", listener: this });
597
+ this.playerId = props.playerId;
598
+ this.preloadHooks = props.preloadHooks;
599
+ this.factoryConfig = props.factoryConfig;
600
+ this.tag = props.tag || "untagged";
601
+ }
602
+
603
+ async prepareForPlayback(): Promise<boolean> {
604
+ // Already prepared — player exists
605
+ if (this.player) {
606
+ return true;
607
+ }
608
+
609
+ // Deduplicate: if preparation is already in flight, await the same promise
610
+ if (this._preparePromise) {
611
+ return this._preparePromise;
612
+ }
613
+
614
+ this._preparePromise = (async (): Promise<boolean> => {
615
+ // 1. Run hooks if configured
616
+ let entry = this.factoryConfig.entry;
617
+
618
+ if (this.preloadHooks?.length) {
619
+ const result = await executePreloadHooks({
620
+ preloadHooks: this.preloadHooks,
621
+ entry,
622
+ });
623
+
624
+ if (result) {
625
+ this.processedEntry = result;
626
+ entry = result;
627
+ } else {
628
+ return false;
629
+ }
630
+ }
631
+
632
+ // 2. Create the player with the correct entry
633
+ const factoryItem = playerFactory({
634
+ player: this.factoryConfig.player,
635
+ playerId: this.factoryConfig.playerId,
636
+ autoplay: false,
637
+ entry,
638
+ muted: this.factoryConfig.muted,
639
+ playerPluginId: this.factoryConfig.playerPluginId,
640
+ screenConfig: this.factoryConfig.screenConfig,
641
+ playerRole: PlayerRole.Cell,
642
+ });
643
+
644
+ if (!factoryItem) {
645
+ throw new Error("Player factory returned null");
646
+ }
647
+
648
+ this.player = factoryItem.controller;
649
+ this.component = factoryItem.Component;
650
+
651
+ // 3. Register callbacks — player now exists
652
+ this.player.addListener({ id: "live-image", listener: this });
653
+
654
+ return true;
655
+ })()
656
+ .then((result) => {
657
+ this._preparePromise = null;
658
+
659
+ return result;
660
+ })
661
+ .catch((error) => {
662
+ this._preparePromise = null;
663
+
664
+ log_error(
665
+ `prepareForPlayback: live-image ${this.playerId}, error preparing for playback: ${error?.message}`,
666
+ { error }
667
+ );
668
+
669
+ throw error;
670
+ });
671
+
672
+ return this._preparePromise;
528
673
  }
529
674
 
530
- public getPlayer = (): Player => {
675
+ public getPlayer = (): Player | null => {
531
676
  return this.player;
532
677
  };
533
678