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

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 (105) hide show
  1. package/Components/Cell/Cell.tsx +6 -0
  2. package/Components/Cell/CellWithFocusable.tsx +9 -0
  3. package/Components/Focusable/Focusable.tsx +4 -2
  4. package/Components/Focusable/FocusableTvOS.tsx +17 -1
  5. package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +1 -0
  6. package/Components/FocusableGroup/FocusableTvOS.tsx +30 -1
  7. package/Components/GeneralContentScreen/GeneralContentScreen.tsx +39 -28
  8. package/Components/GeneralContentScreen/__tests__/GeneralContentScreen.test.tsx +104 -0
  9. package/Components/GeneralContentScreen/utils/__tests__/getScreenDataSource.test.ts +19 -0
  10. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
  11. package/Components/GeneralContentScreen/utils/getScreenDataSource.ts +9 -0
  12. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +19 -10
  13. package/Components/HandlePlayable/HandlePlayable.tsx +16 -29
  14. package/Components/HandlePlayable/utils.ts +31 -0
  15. package/Components/HookRenderer/HookRenderer.tsx +40 -10
  16. package/Components/HookRenderer/__tests__/HookRenderer.test.tsx +60 -0
  17. package/Components/Layout/TV/NavBarContainer.tsx +1 -10
  18. package/Components/Layout/TV/__tests__/__snapshots__/NavBarContainer.test.tsx.snap +7 -12
  19. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +7 -12
  20. package/Components/MasterCell/DefaultComponents/Button.tsx +0 -15
  21. package/Components/MasterCell/DefaultComponents/LiveImage/__tests__/prepareEntry.test.ts +352 -0
  22. package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +136 -0
  23. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +33 -16
  24. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +40 -39
  25. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/Image.test.tsx +95 -0
  26. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +86 -0
  27. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/index.test.ts +141 -0
  28. package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +7 -6
  29. package/Components/MasterCell/DefaultComponents/SecondaryImage/index.ts +1 -1
  30. package/Components/MasterCell/DefaultComponents/Text/index.tsx +2 -6
  31. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +6 -2
  32. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +233 -11
  33. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +19 -15
  34. package/Components/MasterCell/hoc/__tests__/withAsyncRender.test.tsx +219 -0
  35. package/Components/MasterCell/hoc/withAsyncRender.tsx +9 -7
  36. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +17 -9
  37. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +16 -8
  38. package/Components/OfflineHandler/NotificationView/utils.ts +34 -0
  39. package/Components/PlayerContainer/PlayerContainer.tsx +40 -47
  40. package/Components/PreloaderWrapper/__tests__/index.test.tsx +26 -0
  41. package/Components/PreloaderWrapper/index.tsx +15 -0
  42. package/Components/River/ComponentsMap/ComponentsMap.tsx +16 -0
  43. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
  44. package/Components/River/RefreshControl.tsx +36 -13
  45. package/Components/River/RiverItem.tsx +26 -20
  46. package/Components/River/TV/River.tsx +31 -14
  47. package/Components/River/TV/index.tsx +8 -4
  48. package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +30 -0
  49. package/Components/River/TV/utils/index.ts +4 -0
  50. package/Components/River/TV/withFocusableGroupForContent.tsx +71 -0
  51. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +2 -0
  52. package/Components/River/__tests__/componentsMap.test.js +38 -0
  53. package/Components/Screen/__tests__/Screen.test.tsx +1 -0
  54. package/Components/Screen/hooks.ts +73 -3
  55. package/Components/Screen/index.tsx +7 -1
  56. package/Components/Screen/orientationHandler.ts +7 -10
  57. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +46 -0
  58. package/Components/ScreenFeedLoader/__tests__/ScreenFeedLoader.test.tsx +94 -0
  59. package/Components/ScreenFeedLoader/index.ts +1 -0
  60. package/Components/ScreenResolver/__tests__/screenResolver.test.js +24 -0
  61. package/Components/ScreenResolver/hooks/index.ts +3 -0
  62. package/Components/ScreenResolver/hooks/useGetComponent.ts +15 -0
  63. package/Components/ScreenResolver/hooks/useScreenComponentResolver.tsx +90 -0
  64. package/Components/ScreenResolver/index.tsx +15 -117
  65. package/Components/ScreenResolver/utils/__tests__/getScreenTypeProps.test.ts +45 -0
  66. package/Components/ScreenResolver/utils/getScreenTypeProps.ts +43 -0
  67. package/Components/ScreenResolver/utils/index.ts +1 -0
  68. package/Components/ScreenResolver/withDefaultScreenContext.tsx +16 -0
  69. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +25 -0
  70. package/Components/ScreenResolverFeedProvider/__tests__/ScreenResolverFeedProvider.test.tsx +44 -0
  71. package/Components/ScreenResolverFeedProvider/index.ts +1 -0
  72. package/Components/Tabs/TabContent.tsx +7 -4
  73. package/Components/Transitioner/Scene.tsx +9 -15
  74. package/Components/VideoLive/LiveImageManager.ts +199 -54
  75. package/Components/VideoLive/PlayerLiveImageComponent.tsx +31 -33
  76. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +2 -17
  77. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +15 -7
  78. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
  79. package/Components/Viewport/ViewportAware/index.tsx +16 -7
  80. package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
  81. package/Components/ZappUIComponent/index.tsx +12 -6
  82. package/Components/default-cell-renderer/viewTrees/mobile/index.ts +0 -3
  83. package/Components/index.js +1 -1
  84. package/Contexts/ScreenContext/__tests__/index.test.tsx +57 -0
  85. package/Contexts/ScreenContext/index.tsx +71 -19
  86. package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
  87. package/Contexts/ZappHookModalContext/index.tsx +37 -61
  88. package/Contexts/ZappPipesContext/ZappPipesContextFactory.tsx +18 -7
  89. package/Contexts/index.ts +0 -2
  90. package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +1 -0
  91. package/Decorators/ConfigurationWrapper/const.ts +1 -0
  92. package/Decorators/ZappPipesDataConnector/ResolverSelector.tsx +25 -7
  93. package/Decorators/ZappPipesDataConnector/__tests__/ResolverSelector.test.tsx +212 -5
  94. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
  95. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
  96. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
  97. package/events/index.ts +3 -0
  98. package/events/scrollEndReached.ts +15 -0
  99. package/package.json +5 -5
  100. package/Components/MasterCell/DefaultComponents/Text/utils/__tests__/withAdjustedLineHeight.test.ts +0 -46
  101. package/Components/MasterCell/DefaultComponents/Text/utils/index.ts +0 -21
  102. package/Components/PlayerContainer/ErrorDisplay/ErrorDisplay.tsx +0 -57
  103. package/Components/PlayerContainer/ErrorDisplay/index.ts +0 -9
  104. package/Components/River/TV/withTVEventHandler.tsx +0 -27
  105. /package/Components/HookRenderer/{index.tsx → index.ts} +0 -0
@@ -26,11 +26,15 @@ type Props = {
26
26
  componentAnchorPointY: number;
27
27
  headerOffset?: number;
28
28
  extraAnchorPointYOffset?: number;
29
+ componentPaddingTop?: number;
29
30
  }) => void;
30
31
  offsetUpdater: (arg1: string, arg2: number, arg3: number) => number;
31
32
  componentId: string;
32
33
  component: {
33
34
  id: string;
35
+ styles?: {
36
+ component_padding_top?: number;
37
+ };
34
38
  };
35
39
  selected?: boolean;
36
40
  CellRenderer: React.FunctionComponent<any> & {
@@ -178,6 +182,8 @@ export class CellComponent extends React.Component<Props, State> {
178
182
  componentAnchorPointY,
179
183
  headerOffset,
180
184
  extraAnchorPointYOffset,
185
+ componentPaddingTop:
186
+ this.props?.component?.styles?.component_padding_top,
181
187
  });
182
188
  }
183
189
  }
@@ -2,6 +2,7 @@ import * as React from "react";
2
2
 
3
3
  import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
4
4
  import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
5
+ import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
5
6
 
6
7
  import { useCellState } from "../MasterCell/utils";
7
8
  import { FocusableGroup } from "../FocusableGroup";
@@ -26,6 +27,13 @@ type Props = {
26
27
 
27
28
  const addPrefix = (id: string) => `focusable-cell-wrapper-${id}`;
28
29
 
30
+ const wrapperStyles = {
31
+ flex: platformSelect({
32
+ tvos: 1,
33
+ default: undefined,
34
+ }),
35
+ };
36
+
29
37
  export function CellWithFocusable(props: Props) {
30
38
  const {
31
39
  index,
@@ -94,6 +102,7 @@ export function CellWithFocusable(props: Props) {
94
102
  onFocus={onGroupFocus}
95
103
  onBlur={onGroupBlur}
96
104
  skipFocusManagerRegistration={skipFocusManagerRegistration}
105
+ style={wrapperStyles}
97
106
  >
98
107
  <CellWrapper style={styles.cellWrapper}>
99
108
  <CellRenderer
@@ -8,6 +8,8 @@ import { withFocusableContext } from "../../Contexts/FocusableGroupContext/withF
8
8
  import { StyleSheet, ViewStyle } from "react-native";
9
9
  import { AccessibilityManager } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager";
10
10
 
11
+ import { isSearchInputId } from "@applicaster/zapp-react-native-utils/searchUtils";
12
+
11
13
  type Props = {
12
14
  initialFocus?: boolean;
13
15
  id: string;
@@ -106,7 +108,7 @@ class Focusable extends BaseFocusable<Props> {
106
108
  onMouseEnter() {
107
109
  const { id } = this.props;
108
110
 
109
- if (id !== "search_input_group_id") {
111
+ if (!isSearchInputId(id)) {
110
112
  this.mouse = true;
111
113
  this.props?.handleFocus?.({ mouse: true });
112
114
 
@@ -120,7 +122,7 @@ class Focusable extends BaseFocusable<Props> {
120
122
  onMouseLeave() {
121
123
  const { id } = this.props;
122
124
 
123
- if (id !== "search_input_group_id") {
125
+ if (!isSearchInputId(id)) {
124
126
  this.mouse = false;
125
127
  this.blur(null);
126
128
  }
@@ -10,8 +10,12 @@ import {
10
10
  forceFocusableFocus,
11
11
  } from "@applicaster/zapp-react-native-utils/appUtils/focusManager/index.ios";
12
12
  import { findNodeHandle, ViewStyle } from "react-native";
13
+ import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
13
14
 
14
- function noop() {}
15
+ import {
16
+ emitFocused,
17
+ emitNativeRegistered,
18
+ } from "@applicaster/zapp-react-native-utils/appUtils/focusManagerAux/utils/utils.ios";
15
19
 
16
20
  type Props = {
17
21
  id: string;
@@ -54,6 +58,7 @@ export class Focusable extends BaseFocusable<Props> {
54
58
  this.nextFocusableReactTags = {};
55
59
  this.preferredFocus = this.preferredFocus.bind(this);
56
60
  this.measureView = this.measureView.bind(this);
61
+ this.onRegistered = this.onRegistered.bind(this);
57
62
  }
58
63
 
59
64
  /**
@@ -85,6 +90,9 @@ export class Focusable extends BaseFocusable<Props> {
85
90
  });
86
91
  }
87
92
 
93
+ const id: string = nativeEvent.itemID;
94
+ emitFocused(id);
95
+
88
96
  onFocus(nativeEvent);
89
97
  }
90
98
 
@@ -170,6 +178,13 @@ export class Focusable extends BaseFocusable<Props> {
170
178
  });
171
179
  }
172
180
 
181
+ onRegistered({ nativeEvent }) {
182
+ const groupId = nativeEvent?.groupId;
183
+ const id = nativeEvent?.itemId;
184
+
185
+ emitNativeRegistered({ id, groupId, isGroup: false });
186
+ }
187
+
173
188
  render() {
174
189
  const {
175
190
  children,
@@ -204,6 +219,7 @@ export class Focusable extends BaseFocusable<Props> {
204
219
  focusable={isFocusable}
205
220
  {...this.nextFocusableReactTags}
206
221
  {...otherProps}
222
+ onRegistered={this.onRegistered}
207
223
  >
208
224
  {typeof children === "function" ? children(focused) : children}
209
225
  </FocusableItemNative>
@@ -5,6 +5,7 @@ exports[`FocusableTvOS should render correctly 1`] = `
5
5
  groupId={null}
6
6
  itemId={null}
7
7
  onLayout={[Function]}
8
+ onRegistered={[Function]}
8
9
  onViewBlur={[Function]}
9
10
  onViewFocus={[Function]}
10
11
  onViewPress={[Function]}
@@ -2,6 +2,10 @@ import * as React from "react";
2
2
  import { FocusableGroupNative } from "@applicaster/zapp-react-native-ui-components/Components/NativeFocusables";
3
3
  import { BaseFocusable } from "@applicaster/zapp-react-native-ui-components/Components/BaseFocusable";
4
4
  import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
5
+ import { LayoutContext } from "@applicaster/zapp-react-native-tvos-app/Context/LayoutContext";
6
+ import { useRoute } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useRoute";
7
+ import { isScreenPlayable } from "@applicaster/zapp-react-native-utils/navigationUtils/itemTypes";
8
+ import { emitNativeRegistered } from "@applicaster/zapp-react-native-utils/appUtils/focusManagerAux/utils/utils.ios";
5
9
 
6
10
  const { log_verbose } = createLogger({
7
11
  subsystem: "General",
@@ -33,9 +37,16 @@ type Props = {
33
37
  screenData: { screenId: string; parentScreenId: string };
34
38
  };
35
39
 
36
- export class FocusableGroup extends BaseFocusable<Props> {
40
+ class FocusableGroupComponent extends BaseFocusable<Props> {
37
41
  public readonly isGroup: boolean = true;
38
42
 
43
+ onRegistered = ({ nativeEvent }) => {
44
+ const groupId = nativeEvent?.groupId;
45
+ const id = nativeEvent?.itemId;
46
+
47
+ emitNativeRegistered({ id, groupId, isGroup: true });
48
+ };
49
+
39
50
  render() {
40
51
  const {
41
52
  children,
@@ -68,9 +79,27 @@ export class FocusableGroup extends BaseFocusable<Props> {
68
79
  onGroupBlur={onGroupBlur}
69
80
  style={style}
70
81
  {...otherProps}
82
+ onRegistered={this.onRegistered}
71
83
  >
72
84
  {children}
73
85
  </FocusableGroupNative>
74
86
  );
75
87
  }
76
88
  }
89
+
90
+ export const withFocusDisabled = (Component) => {
91
+ return function WithFocusDisabled(props) {
92
+ // @ts-ignore
93
+ const { screenFocusBlocked } = React.useContext(LayoutContext.ReactContext);
94
+
95
+ const { pathname } = useRoute();
96
+
97
+ const isPlayerPresented = isScreenPlayable(pathname);
98
+
99
+ const blockScreenFocus = isPlayerPresented === false && screenFocusBlocked;
100
+
101
+ return <Component {...props} isFocusDisabled={blockScreenFocus} />;
102
+ };
103
+ };
104
+
105
+ export const FocusableGroup = withFocusDisabled(FocusableGroupComponent);
@@ -12,12 +12,26 @@ import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
12
12
  import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
13
13
  import { ScreenTrackedViewPositionsContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenTrackedViewPositionsContext";
14
14
  import { useEventAlerts } from "./utils/useEventAlerts";
15
-
16
- const { log_info } = createLogger({
15
+ import {
16
+ selectRiverById,
17
+ useAppSelector,
18
+ } from "@applicaster/zapp-react-native-redux";
19
+ import { getScreenDataSource } from "./utils/getScreenDataSource";
20
+ import { ScreenResolverFeedProvider } from "../ScreenResolverFeedProvider/ScreenResolverFeedProvider";
21
+
22
+ const { log_debug } = createLogger({
17
23
  category: "ScreenContainer",
18
24
  subsystem: "General",
19
25
  });
20
26
 
27
+ /** Provides screen-feed from general-screen configuration (if defined) */
28
+ const useFeedData = (id) => {
29
+ const river = useAppSelector((state) => selectRiverById(state, id));
30
+ const feedData = getScreenDataSource(river);
31
+
32
+ return feedData;
33
+ };
34
+
21
35
  export const GeneralContentScreen = ({
22
36
  feed,
23
37
  screenId,
@@ -54,20 +68,15 @@ export const GeneralContentScreen = ({
54
68
  useEffect(() => {
55
69
  if (!riverActionProvidersReady) {
56
70
  if (actionsInitialStateSetters.length > 0) {
57
- log_info(
58
- "ScreenContainer: starting to check river action providers to initialize",
59
- { actionsInitialStateSetters }
60
- );
61
-
62
71
  allSettled(actionsInitialStateSetters).finally(() => {
63
- log_info(
72
+ log_debug(
64
73
  "ScreenContainer: action provider ready, completed. Starting to present screen"
65
74
  );
66
75
 
67
76
  setRiverActionProvidersReady(true);
68
77
  });
69
78
  } else {
70
- log_info(
79
+ log_debug(
71
80
  "ScreenContainer: no action provider to check, completed. Starting to present screen"
72
81
  );
73
82
 
@@ -108,24 +117,26 @@ export const GeneralContentScreen = ({
108
117
  if (!isReady || isNilOrEmpty(components || uiComponents)) return null;
109
118
 
110
119
  return (
111
- <ScreenTrackedViewPositionsContext.Provider>
112
- <CellTapContext.Provider value={contextValue}>
113
- <ComponentsMap
114
- feed={feed}
115
- riverId={screenId}
116
- groupId={groupId || `general-content-screen-${screenId}`}
117
- riverComponents={components || uiComponents}
118
- scrollViewExtraProps={scrollViewExtraProps}
119
- getStaticComponentFeed={getStaticComponentFeed}
120
- extraAnchorPointYOffset={extraAnchorPointYOffset}
121
- isScreenWrappedInContainer={isScreenWrappedInContainer}
122
- parentFocus={parentFocus}
123
- focused={focused}
124
- containerHeight={containerHeight}
125
- preferredFocus={preferredFocus}
126
- {...componentsMapExtraProps}
127
- />
128
- </CellTapContext.Provider>
129
- </ScreenTrackedViewPositionsContext.Provider>
120
+ <ScreenResolverFeedProvider id={screenId} useFeedData={useFeedData}>
121
+ <ScreenTrackedViewPositionsContext.Provider>
122
+ <CellTapContext.Provider value={contextValue}>
123
+ <ComponentsMap
124
+ feed={feed}
125
+ riverId={screenId}
126
+ groupId={groupId || `general-content-screen-${screenId}`}
127
+ riverComponents={components || uiComponents}
128
+ scrollViewExtraProps={scrollViewExtraProps}
129
+ getStaticComponentFeed={getStaticComponentFeed}
130
+ extraAnchorPointYOffset={extraAnchorPointYOffset}
131
+ isScreenWrappedInContainer={isScreenWrappedInContainer}
132
+ parentFocus={parentFocus}
133
+ focused={focused}
134
+ containerHeight={containerHeight}
135
+ preferredFocus={preferredFocus}
136
+ {...componentsMapExtraProps}
137
+ />
138
+ </CellTapContext.Provider>
139
+ </ScreenTrackedViewPositionsContext.Provider>
140
+ </ScreenResolverFeedProvider>
130
141
  );
131
142
  };
@@ -0,0 +1,104 @@
1
+ import React from "react";
2
+ import { render } from "@testing-library/react-native";
3
+ import { GeneralContentScreen } from "../GeneralContentScreen";
4
+
5
+ const mockUseAppSelector = jest.fn();
6
+ const mockSelectRiverById = jest.fn();
7
+ const mockProviderSpy = jest.fn();
8
+
9
+ jest.mock("../../River/ComponentsMap", () => ({
10
+ ComponentsMap: () => {
11
+ const React = require("react");
12
+ const { View } = require("react-native");
13
+
14
+ return <View testID="components-map" />;
15
+ },
16
+ }));
17
+
18
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/actions", () => ({
19
+ useActions: jest.fn(() => jest.fn()),
20
+ }));
21
+
22
+ jest.mock("../utils", () => ({
23
+ logger: { warn: jest.fn() },
24
+ whenMatchingType: jest.fn((_type, value) => value),
25
+ }));
26
+
27
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/layout", () => ({
28
+ useLayoutVersion: jest.fn(() => false),
29
+ }));
30
+
31
+ jest.mock(
32
+ "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenData",
33
+ () => ({
34
+ useScreenData: jest.fn(() => ({
35
+ ui_components: [{ id: "ui-component" }],
36
+ general: {},
37
+ })),
38
+ })
39
+ );
40
+
41
+ jest.mock("../utils/useCurationAPI", () => ({
42
+ useCurationAPI: jest.fn(() => [{ id: "curation-component" }]),
43
+ }));
44
+
45
+ jest.mock("@applicaster/quick-brick-core/App/ActionSetters", () => ({
46
+ useRiverInitialState: jest.fn(() => []),
47
+ }));
48
+
49
+ jest.mock("../utils/useEventAlerts", () => ({
50
+ useEventAlerts: jest.fn(),
51
+ }));
52
+
53
+ jest.mock("@applicaster/zapp-react-native-redux", () => ({
54
+ useAppSelector: (...args) => mockUseAppSelector(...args),
55
+ selectRiverById: (...args) => mockSelectRiverById(...args),
56
+ }));
57
+
58
+ jest.mock("../utils/getScreenDataSource", () => ({
59
+ getScreenDataSource: jest.fn(() => ({
60
+ source: "https://feed",
61
+ mapping: {},
62
+ })),
63
+ }));
64
+
65
+ jest.mock(
66
+ "../../ScreenResolverFeedProvider/ScreenResolverFeedProvider",
67
+ () => ({
68
+ ScreenResolverFeedProvider: ({ id, useFeedData, children }) => {
69
+ const React = require("react");
70
+ const { View } = require("react-native");
71
+
72
+ mockProviderSpy(id, useFeedData);
73
+ useFeedData(id);
74
+
75
+ return <View testID="screen-resolver-feed-provider">{children}</View>;
76
+ },
77
+ })
78
+ );
79
+
80
+ describe("GeneralContentScreen", () => {
81
+ beforeEach(() => {
82
+ jest.clearAllMocks();
83
+ mockUseAppSelector.mockImplementation((selector) => selector({}));
84
+ mockSelectRiverById.mockReturnValue({ id: "screen-1" });
85
+ });
86
+
87
+ it("wraps content with ScreenResolverFeedProvider and renders ComponentsMap", () => {
88
+ const { getByTestId } = render(
89
+ <GeneralContentScreen
90
+ screenId="screen-1"
91
+ feed={null}
92
+ components={[{ id: "component-1" }]}
93
+ />
94
+ );
95
+
96
+ expect(getByTestId("screen-resolver-feed-provider")).toBeDefined();
97
+ expect(getByTestId("components-map")).toBeDefined();
98
+
99
+ expect(mockProviderSpy).toHaveBeenCalledWith(
100
+ "screen-1",
101
+ expect.any(Function)
102
+ );
103
+ });
104
+ });
@@ -0,0 +1,19 @@
1
+ import { getScreenDataSource } from "../getScreenDataSource";
2
+
3
+ describe("getScreenDataSource", () => {
4
+ it("returns screen_feed data when present", () => {
5
+ const result = getScreenDataSource({
6
+ data: {
7
+ screen_feed: {
8
+ source: "https://feed",
9
+ },
10
+ },
11
+ });
12
+
13
+ expect(result).toEqual({ source: "https://feed" });
14
+ });
15
+
16
+ it("returns undefined when screen_feed is missing", () => {
17
+ expect(getScreenDataSource({ data: {} })).toBeUndefined();
18
+ });
19
+ });
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import { renderHook } from "@testing-library/react-hooks";
2
+ import { renderHook } from "@testing-library/react-native";
3
3
 
4
4
  import {
5
5
  getTransformedPreset,
@@ -0,0 +1,9 @@
1
+ import { get } from "@applicaster/zapp-react-native-utils/utils";
2
+
3
+ const lookupPath = ["data", "screen_feed"];
4
+
5
+ export const getScreenDataSource = (
6
+ screenData: any
7
+ ): Option<ZappDataSource> => {
8
+ return get(screenData, lookupPath) as ZappDataSource | undefined;
9
+ };
@@ -1,4 +1,4 @@
1
- import { all, equals, path, prop, isEmpty, pluck, values } from "ramda";
1
+ import { all, equals, isEmpty, path, pluck, prop, values } from "ramda";
2
2
 
3
3
  import { useEffect, useMemo } from "react";
4
4
 
@@ -9,10 +9,9 @@ import {
9
9
  import { isEmptyOrNil } from "@applicaster/zapp-react-native-utils/cellUtils";
10
10
  import { Categories } from "./logger";
11
11
  import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
12
- import { useRoute } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useRoute";
12
+ import { useScreenContext } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
13
13
 
14
14
  import {
15
- ZappPipesEntryContext,
16
15
  ZappPipesScreenContext,
17
16
  ZappPipesSearchContext,
18
17
  } from "@applicaster/zapp-react-native-ui-components/Contexts";
@@ -24,6 +23,7 @@ import {
24
23
 
25
24
  import { produce } from "immer";
26
25
  import { useLoadPipesDataDispatch } from "@applicaster/zapp-react-native-utils/reactHooks";
26
+
27
27
  // types reference
28
28
 
29
29
  declare interface CurationEntry {
@@ -36,6 +36,7 @@ type Feeds = Record<string, ZappPipesData>;
36
36
  type LayoutPresets = PresetsMapping["presets_mappings"];
37
37
 
38
38
  const TABS_SCREEN_TYPE = "tabs_screen";
39
+ const QB_TABS_SCREEN_TYPE = "quick-brick-tabs";
39
40
  const SMART_COMPONENT_TYPE = "quick-brick-smart-component";
40
41
  const SOURCE_PATH = ["data", "source"];
41
42
  const MAPPING_PATH = ["data", "mapping"];
@@ -54,7 +55,10 @@ export const getTransformedPreset = (
54
55
  const presetComponent = layoutPresets?.[preset?.preset_name];
55
56
 
56
57
  if (!presetComponent) {
57
- logger.log_error("Preset missing or wrong data format", { entry: preset });
58
+ logger.log_error(
59
+ `Preset "${preset?.preset_name}" missing or wrong data format`,
60
+ { entry: preset }
61
+ );
58
62
 
59
63
  return;
60
64
  }
@@ -131,16 +135,21 @@ export const useCurationAPI = (
131
135
  [components]
132
136
  );
133
137
 
134
- const { pathname } = useRoute();
135
138
  const [searchContext] = ZappPipesSearchContext.useZappPipesContext();
136
139
  const [screenContext] = ZappPipesScreenContext.useZappPipesContext();
137
140
 
138
- const isNestedScreen = screenContext?.type === TABS_SCREEN_TYPE;
141
+ const screenContextType = screenContext?.type;
139
142
 
140
- const [entryContext] = ZappPipesEntryContext.useZappPipesContext(
141
- pathname,
142
- isNestedScreen
143
- );
143
+ const isNestedScreen =
144
+ screenContextType === TABS_SCREEN_TYPE ||
145
+ screenContextType === QB_TABS_SCREEN_TYPE;
146
+
147
+ const screenContextData = useScreenContext();
148
+
149
+ const entryContext = ((isNestedScreen && screenContextData?.nested?.entry
150
+ ? screenContextData?.nested?.entry
151
+ : (screenContextData?.entry?.payload ?? screenContextData?.entry)) ||
152
+ {}) as ZappEntry;
144
153
 
145
154
  const urlsMap = useMemo<{ [key: string]: string }>(() => {
146
155
  const map = {};
@@ -5,8 +5,6 @@ import {
5
5
  usePlugins,
6
6
  } from "@applicaster/zapp-react-native-redux/hooks";
7
7
  import {
8
- useDimensions,
9
- useIsTablet as isTablet,
10
8
  useNavigation,
11
9
  useRivers,
12
10
  } from "@applicaster/zapp-react-native-utils/reactHooks";
@@ -15,8 +13,8 @@ import { BufferAnimation } from "../PlayerContainer/BufferAnimation";
15
13
  import { PlayerContainer } from "../PlayerContainer";
16
14
  import { useModalSize } from "../VideoModal/hooks";
17
15
  import { ViewStyle } from "react-native";
18
- import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
19
16
  import { findCastPlugin, getPlayer } from "./utils";
17
+ import { useWaitForValidOrientation } from "../Screen/hooks";
20
18
 
21
19
  type Props = {
22
20
  item: ZappEntry;
@@ -31,13 +29,6 @@ type PlayableComponent = {
31
29
  Component: React.ComponentType<any>;
32
30
  };
33
31
 
34
- const dimensionsContext: "window" | "screen" = platformSelect({
35
- android_tv: "window",
36
- amazon: "window",
37
- // eslint-disable-next-line react-hooks/rules-of-hooks
38
- default: isTablet() ? "window" : "screen", // on tablet, window represents correct values, on phone it's not as the screen could be rotated
39
- });
40
-
41
32
  export function HandlePlayable({
42
33
  item,
43
34
  isModal,
@@ -97,27 +88,23 @@ export function HandlePlayable({
97
88
  });
98
89
  }, [casting]);
99
90
 
100
- const { width: screenWidth, height: screenHeight } =
101
- useDimensions(dimensionsContext);
102
-
103
91
  const modalSize = useModalSize();
104
92
 
105
- const style = React.useMemo(
106
- () =>
107
- ({
108
- width: isModal
109
- ? modalSize.width
110
- : mode === "PIP"
111
- ? "100%"
112
- : screenWidth,
113
- height: isModal
114
- ? modalSize.height
115
- : mode === "PIP"
116
- ? "100%"
117
- : screenHeight,
118
- }) as ViewStyle,
119
- [screenWidth, screenHeight, modalSize, isModal, mode]
120
- );
93
+ const isOrientationReady = useWaitForValidOrientation();
94
+
95
+ const style = React.useMemo(() => {
96
+ const isFullScreenReady =
97
+ mode === "PIP" || (mode === "FULLSCREEN" && isOrientationReady);
98
+
99
+ const getDimensionValue = (value: string | number) => {
100
+ return isModal ? value : isFullScreenReady ? "100%" : 0; // do not show player, until full screen mode is ready
101
+ };
102
+
103
+ return {
104
+ width: getDimensionValue(modalSize.width),
105
+ height: getDimensionValue(modalSize.height),
106
+ } as ViewStyle;
107
+ }, [modalSize, isModal, mode, isOrientationReady]);
121
108
 
122
109
  const Component = playable?.Component;
123
110
 
@@ -5,6 +5,14 @@ import {
5
5
 
6
6
  import { CHROMECAST_PLUGIN_ID, YOUTUBE_PLUGIN_ID } from "./const";
7
7
  import { omit } from "@applicaster/zapp-react-native-utils/utils";
8
+ import { getXray } from "@applicaster/zapp-react-native-utils/logger";
9
+
10
+ const { Logger } = getXray();
11
+
12
+ const logger = new Logger(
13
+ "QuickBrick",
14
+ "packages/zapp-react-native-ui-components/Components/HandlePlayable"
15
+ );
8
16
 
9
17
  const getPlayerModuleProperties = (PlayerModule: ZappPlugin) => {
10
18
  if (PlayerModule?.Component && typeof PlayerModule.Component === "object") {
@@ -52,10 +60,25 @@ export const getPlayer = (
52
60
  if (type) {
53
61
  PlayerModule = findPluginByIdentifier(type, plugins)?.module;
54
62
 
63
+ if (!PlayerModule) {
64
+ logger.error({
65
+ message:
66
+ "PlayerModule is undefined – type mapping may be wrong or type not set for player",
67
+ data: {
68
+ type,
69
+ screen_id,
70
+ item_type_value: item?.type?.value,
71
+ },
72
+ });
73
+
74
+ return [null, {}];
75
+ }
76
+
55
77
  return getPlayerWithModuleProperties(PlayerModule);
56
78
  }
57
79
  }
58
80
 
81
+ // TODO: Probably should be removed, Youtube plugin is deprecated
59
82
  if (item?.content?.type === "youtube-id") {
60
83
  PlayerModule = findYoutubePlugin(plugins)?.module;
61
84
 
@@ -70,5 +93,13 @@ export const getPlayer = (
70
93
  )
71
94
  );
72
95
 
96
+ if (!PlayerModule) {
97
+ logger.error({
98
+ message: "PlayerModule is undefined – playable plugin not found",
99
+ });
100
+
101
+ return [null, {}];
102
+ }
103
+
73
104
  return getPlayerWithModuleProperties(PlayerModule);
74
105
  };