@applicaster/zapp-react-native-ui-components 15.0.0-rc.99 → 15.1.0-rc.2

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 (114) hide show
  1. package/Components/BaseFocusable/index.ios.ts +2 -12
  2. package/Components/Cell/FocusableWrapper.tsx +0 -3
  3. package/Components/Cell/TvOSCellComponent.tsx +0 -5
  4. package/Components/Focusable/Focusable.tsx +2 -4
  5. package/Components/Focusable/FocusableTvOS.tsx +1 -18
  6. package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +0 -1
  7. package/Components/FocusableGroup/FocusableTvOS.tsx +1 -30
  8. package/Components/GeneralContentScreen/GeneralContentScreen.tsx +39 -28
  9. package/Components/GeneralContentScreen/__tests__/GeneralContentScreen.test.tsx +104 -0
  10. package/Components/GeneralContentScreen/utils/__tests__/getScreenDataSource.test.ts +19 -0
  11. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
  12. package/Components/GeneralContentScreen/utils/getScreenDataSource.ts +9 -0
  13. package/Components/HandlePlayable/HandlePlayable.tsx +24 -42
  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/LayoutBackground.tsx +2 -5
  18. package/Components/Layout/TV/ScreenContainer.tsx +6 -2
  19. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +5 -0
  20. package/Components/Layout/TV/index.tsx +4 -3
  21. package/Components/Layout/TV/index.web.tsx +4 -3
  22. package/Components/LinkHandler/LinkHandler.tsx +2 -2
  23. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +10 -4
  24. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +1 -5
  25. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +3 -11
  26. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +1 -9
  27. package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +14 -15
  28. package/Components/MasterCell/DefaultComponents/LiveImage/__tests__/prepareEntry.test.ts +352 -0
  29. package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +136 -0
  30. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +34 -16
  31. package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +6 -7
  32. package/Components/MasterCell/DefaultComponents/Text/index.tsx +2 -6
  33. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +2 -6
  34. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +11 -233
  35. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +15 -19
  36. package/Components/Navigator/StackNavigator.tsx +6 -0
  37. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +2 -2
  38. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +18 -17
  39. package/Components/OfflineHandler/__tests__/index.test.tsx +18 -27
  40. package/Components/PlayerContainer/PlayerContainer.tsx +14 -32
  41. package/Components/PreloaderWrapper/__tests__/index.test.tsx +26 -0
  42. package/Components/PreloaderWrapper/index.tsx +15 -0
  43. package/Components/River/ComponentsMap/ComponentsMap.tsx +3 -4
  44. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
  45. package/Components/River/RefreshControl.tsx +9 -3
  46. package/Components/River/RiverItem.tsx +26 -20
  47. package/Components/River/TV/River.tsx +14 -31
  48. package/Components/River/TV/index.tsx +4 -8
  49. package/Components/River/TV/withTVEventHandler.tsx +36 -0
  50. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +0 -1
  51. package/Components/River/__tests__/componentsMap.test.js +0 -38
  52. package/Components/Screen/TV/index.web.tsx +2 -4
  53. package/Components/Screen/__tests__/Screen.test.tsx +43 -65
  54. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +44 -68
  55. package/Components/Screen/hooks.ts +76 -5
  56. package/Components/Screen/index.tsx +10 -3
  57. package/Components/Screen/orientationHandler.ts +3 -3
  58. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +46 -0
  59. package/Components/ScreenFeedLoader/__tests__/ScreenFeedLoader.test.tsx +94 -0
  60. package/Components/ScreenFeedLoader/index.ts +1 -0
  61. package/Components/ScreenResolver/__tests__/screenResolver.test.js +24 -0
  62. package/Components/ScreenResolver/hooks/index.ts +3 -0
  63. package/Components/ScreenResolver/hooks/useGetComponent.ts +15 -0
  64. package/Components/ScreenResolver/hooks/useScreenComponentResolver.tsx +90 -0
  65. package/Components/ScreenResolver/index.tsx +9 -115
  66. package/Components/ScreenResolver/utils/__tests__/getScreenTypeProps.test.ts +45 -0
  67. package/Components/ScreenResolver/utils/getScreenTypeProps.ts +43 -0
  68. package/Components/ScreenResolver/utils/index.ts +1 -0
  69. package/Components/ScreenResolver/withDefaultScreenContext.tsx +16 -0
  70. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +25 -0
  71. package/Components/ScreenResolverFeedProvider/__tests__/ScreenResolverFeedProvider.test.tsx +44 -0
  72. package/Components/ScreenResolverFeedProvider/index.ts +1 -0
  73. package/Components/ScreenRevealManager/ScreenRevealManager.ts +8 -40
  74. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +69 -86
  75. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +4 -1
  76. package/Components/Tabs/TabContent.tsx +4 -7
  77. package/Components/Transitioner/Scene.tsx +9 -15
  78. package/Components/Transitioner/index.js +3 -3
  79. package/Components/VideoLive/LiveImageManager.ts +199 -54
  80. package/Components/VideoLive/PlayerLiveImageComponent.tsx +31 -33
  81. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +2 -17
  82. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +5 -5
  83. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +7 -15
  84. package/Components/VideoModal/utils.ts +9 -12
  85. package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
  86. package/Components/ZappFrameworkComponents/BarView/BarView.tsx +6 -4
  87. package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +2 -2
  88. package/Components/ZappUIComponent/index.tsx +12 -6
  89. package/Components/index.js +1 -1
  90. package/Contexts/ScreenContext/__tests__/index.test.tsx +57 -0
  91. package/Contexts/ScreenContext/index.tsx +64 -26
  92. package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
  93. package/Contexts/ZappPipesContext/ZappPipesContextFactory.tsx +18 -7
  94. package/Decorators/Analytics/index.tsx +5 -6
  95. package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +0 -1
  96. package/Decorators/ConfigurationWrapper/const.ts +0 -1
  97. package/Decorators/ZappPipesDataConnector/ResolverSelector.tsx +25 -7
  98. package/Decorators/ZappPipesDataConnector/__tests__/ResolverSelector.test.tsx +212 -5
  99. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
  100. package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
  101. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
  102. package/Helpers/DataSourceHelper/index.js +19 -0
  103. package/package.json +5 -5
  104. package/Components/MasterCell/DefaultComponents/Text/utils/__tests__/withAdjustedLineHeight.test.ts +0 -46
  105. package/Components/MasterCell/DefaultComponents/Text/utils/index.ts +0 -21
  106. package/Components/PlayerContainer/ErrorDisplay/ErrorDisplay.tsx +0 -57
  107. package/Components/PlayerContainer/ErrorDisplay/index.ts +0 -9
  108. package/Components/PlayerContainer/useRestrictMobilePlayback.tsx +0 -101
  109. package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +0 -30
  110. package/Components/River/TV/utils/index.ts +0 -4
  111. package/Components/River/TV/withFocusableGroupForContent.tsx +0 -71
  112. package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +0 -80
  113. package/Helpers/DataSourceHelper/index.ts +0 -19
  114. /package/Components/HookRenderer/{index.tsx → index.ts} +0 -0
@@ -7,15 +7,30 @@ import { UrlFeedResolver } from "../resolvers/UrlFeedResolver";
7
7
  import { NullFeedResolver } from "../resolvers/NullFeedResolver";
8
8
 
9
9
  jest.mock("../resolvers/StaticFeedResolver", () => ({
10
- StaticFeedResolver: jest.fn(() => null),
10
+ StaticFeedResolver: jest.fn(({ children }) =>
11
+ children({
12
+ zappPipesData: { loading: true, data: null, error: null },
13
+ reloadData: jest.fn(),
14
+ })
15
+ ),
11
16
  }));
12
17
 
13
18
  jest.mock("../resolvers/UrlFeedResolver", () => ({
14
- UrlFeedResolver: jest.fn(() => null),
19
+ UrlFeedResolver: jest.fn(({ children }) =>
20
+ children({
21
+ zappPipesData: { loading: true, data: null, error: null },
22
+ reloadData: jest.fn(),
23
+ })
24
+ ),
15
25
  }));
16
26
 
17
27
  jest.mock("../resolvers/NullFeedResolver", () => ({
18
- NullFeedResolver: jest.fn(() => null),
28
+ NullFeedResolver: jest.fn(({ children }) =>
29
+ children({
30
+ zappPipesData: { loading: false, data: null, error: null },
31
+ reloadData: jest.fn(),
32
+ })
33
+ ),
19
34
  }));
20
35
 
21
36
  const testPlugin = {
@@ -52,7 +67,7 @@ describe("ResolverSelector", () => {
52
67
  expect.objectContaining({
53
68
  getStaticComponentFeed: props.getStaticComponentFeed,
54
69
  component: mockComponent,
55
- children: mockChildren,
70
+ children: expect.any(Function),
56
71
  }),
57
72
  expect.anything()
58
73
  );
@@ -151,7 +166,10 @@ describe("ResolverSelector", () => {
151
166
  render(<ResolverSelector {...props} />);
152
167
 
153
168
  expect(StaticFeedResolver).toHaveBeenCalledWith(
154
- expect.objectContaining(props),
169
+ expect.objectContaining({
170
+ ...props,
171
+ children: expect.any(Function),
172
+ }),
155
173
  expect.anything()
156
174
  );
157
175
  });
@@ -202,4 +220,193 @@ describe("ResolverSelector", () => {
202
220
  expect(UrlFeedResolver).not.toHaveBeenCalled();
203
221
  expect(NullFeedResolver).not.toHaveBeenCalled();
204
222
  });
223
+
224
+ describe("Fallback Logic", () => {
225
+ it("should fallback to UrlFeedResolver when StaticFeedResolver returns null and feedUrl is present", () => {
226
+ const props = {
227
+ getStaticComponentFeed: jest.fn(),
228
+ feedUrl: "https://example.com/feed",
229
+ component: mockComponent,
230
+ children: mockChildren,
231
+ riverId: "test-river",
232
+ };
233
+
234
+ (StaticFeedResolver as jest.Mock).mockImplementation(({ children }) =>
235
+ children({
236
+ zappPipesData: { loading: false, data: null, error: null },
237
+ reloadData: jest.fn(),
238
+ })
239
+ );
240
+
241
+ render(<ResolverSelector {...props} />);
242
+
243
+ expect(StaticFeedResolver).toHaveBeenCalled();
244
+
245
+ expect(UrlFeedResolver).toHaveBeenCalledWith(
246
+ expect.objectContaining({
247
+ feedUrl: props.feedUrl,
248
+ component: mockComponent,
249
+ children: mockChildren,
250
+ }),
251
+ expect.anything()
252
+ );
253
+ });
254
+
255
+ it("should fallback to UrlFeedResolver when StaticFeedResolver returns null and component.data.source is present", () => {
256
+ const componentWithSource = {
257
+ ...mockComponent,
258
+ data: {
259
+ source: "data-source",
260
+ },
261
+ };
262
+
263
+ const props = {
264
+ getStaticComponentFeed: jest.fn(),
265
+ component: componentWithSource,
266
+ children: mockChildren,
267
+ riverId: "test-river",
268
+ };
269
+
270
+ (StaticFeedResolver as jest.Mock).mockImplementation(({ children }) =>
271
+ children({
272
+ zappPipesData: { loading: false, data: null, error: null },
273
+ reloadData: jest.fn(),
274
+ })
275
+ );
276
+
277
+ render(<ResolverSelector {...props} />);
278
+
279
+ expect(StaticFeedResolver).toHaveBeenCalled();
280
+
281
+ expect(UrlFeedResolver).toHaveBeenCalledWith(
282
+ expect.objectContaining({
283
+ component: componentWithSource,
284
+ children: mockChildren,
285
+ }),
286
+ expect.anything()
287
+ );
288
+ });
289
+
290
+ it("should fallback to NullFeedResolver when StaticFeedResolver returns null and no feedUrl/source is present", () => {
291
+ const props = {
292
+ getStaticComponentFeed: jest.fn(),
293
+ component: mockComponent,
294
+ children: mockChildren,
295
+ riverId: "test-river",
296
+ };
297
+
298
+ (StaticFeedResolver as jest.Mock).mockImplementation(({ children }) =>
299
+ children({
300
+ zappPipesData: { loading: false, data: null, error: null },
301
+ reloadData: jest.fn(),
302
+ })
303
+ );
304
+
305
+ render(<ResolverSelector {...props} />);
306
+
307
+ expect(StaticFeedResolver).toHaveBeenCalled();
308
+ expect(UrlFeedResolver).not.toHaveBeenCalled();
309
+
310
+ expect(NullFeedResolver).toHaveBeenCalledWith(
311
+ expect.objectContaining({
312
+ children: mockChildren,
313
+ }),
314
+ expect.anything()
315
+ );
316
+ });
317
+
318
+ it("should NOT fallback and call children with static data when StaticFeedResolver returns data", () => {
319
+ const staticData = { entry: [{ id: "1" }] };
320
+
321
+ const props = {
322
+ getStaticComponentFeed: jest.fn(),
323
+ feedUrl: "https://example.com/feed",
324
+ component: mockComponent,
325
+ children: mockChildren,
326
+ riverId: "test-river",
327
+ };
328
+
329
+ (StaticFeedResolver as jest.Mock).mockImplementation(({ children }) =>
330
+ children({
331
+ zappPipesData: { loading: false, data: staticData, error: null },
332
+ reloadData: jest.fn(),
333
+ })
334
+ );
335
+
336
+ render(<ResolverSelector {...props} />);
337
+
338
+ expect(StaticFeedResolver).toHaveBeenCalled();
339
+ expect(UrlFeedResolver).not.toHaveBeenCalled();
340
+
341
+ expect(mockChildren).toHaveBeenCalledWith(
342
+ expect.objectContaining({
343
+ zappPipesData: expect.objectContaining({
344
+ data: staticData,
345
+ }),
346
+ })
347
+ );
348
+ });
349
+
350
+ it("should NOT fallback and call children with loading state when StaticFeedResolver is loading", () => {
351
+ const props = {
352
+ getStaticComponentFeed: jest.fn(),
353
+ feedUrl: "https://example.com/feed",
354
+ component: mockComponent,
355
+ children: mockChildren,
356
+ riverId: "test-river",
357
+ };
358
+
359
+ (StaticFeedResolver as jest.Mock).mockImplementation(({ children }) =>
360
+ children({
361
+ zappPipesData: { loading: true, data: null, error: null },
362
+ reloadData: jest.fn(),
363
+ })
364
+ );
365
+
366
+ render(<ResolverSelector {...props} />);
367
+
368
+ expect(StaticFeedResolver).toHaveBeenCalled();
369
+ expect(UrlFeedResolver).not.toHaveBeenCalled();
370
+
371
+ expect(mockChildren).toHaveBeenCalledWith(
372
+ expect.objectContaining({
373
+ zappPipesData: expect.objectContaining({
374
+ loading: true,
375
+ }),
376
+ })
377
+ );
378
+ });
379
+
380
+ it("should NOT fallback and call children with error when StaticFeedResolver returns error", () => {
381
+ const error = new Error("Static error");
382
+
383
+ const props = {
384
+ getStaticComponentFeed: jest.fn(),
385
+ feedUrl: "https://example.com/feed",
386
+ component: mockComponent,
387
+ children: mockChildren,
388
+ riverId: "test-river",
389
+ };
390
+
391
+ (StaticFeedResolver as jest.Mock).mockImplementation(({ children }) =>
392
+ children({
393
+ zappPipesData: { loading: false, data: null, error },
394
+ reloadData: jest.fn(),
395
+ })
396
+ );
397
+
398
+ render(<ResolverSelector {...props} />);
399
+
400
+ expect(StaticFeedResolver).toHaveBeenCalled();
401
+ expect(UrlFeedResolver).not.toHaveBeenCalled();
402
+
403
+ expect(mockChildren).toHaveBeenCalledWith(
404
+ expect.objectContaining({
405
+ zappPipesData: expect.objectContaining({
406
+ error,
407
+ }),
408
+ })
409
+ );
410
+ });
411
+ });
205
412
  });
@@ -3,7 +3,7 @@ import { renderWithProviders } from "@applicaster/zapp-react-native-utils/testUt
3
3
 
4
4
  import * as zappPipesRedux from "@applicaster/zapp-react-native-redux/ZappPipes";
5
5
  import configureStore from "redux-mock-store";
6
- import { thunk } from "redux-thunk";
6
+ import thunk from "redux-thunk";
7
7
 
8
8
  import { zappPipesDataConnector } from "../index";
9
9
 
@@ -2,7 +2,7 @@
2
2
  /// <reference types="@applicaster/zapp-react-native-ui-components" />
3
3
  import React from "react";
4
4
  import { useRoute } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
5
- import { usePlugins } from "@applicaster/zapp-react-native-redux/hooks";
5
+ import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
6
6
  import { ZappPipesSearchContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
7
7
  import { useScreenContext } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
8
8
  import { ResolverSelector } from "./ResolverSelector";
@@ -23,7 +23,7 @@ export function zappPipesDataConnector(
23
23
  ) {
24
24
  return function WrappedWithZappPipesData(props: Props) {
25
25
  const { screenData } = useRoute();
26
- const plugins = usePlugins();
26
+ const { plugins } = usePickFromState(["plugins"]);
27
27
  const screenContextData = useScreenContext();
28
28
 
29
29
  const {
@@ -76,7 +76,7 @@ export function StaticFeedResolver({
76
76
 
77
77
  const zappPipesDataProps = useMemo(
78
78
  () => ({
79
- zappPipesData: { url, loading, data, error }, // todo: add applyItemLimit
79
+ zappPipesData: { url, loading, data, error },
80
80
  reloadData,
81
81
  loadNextData: undefined, // Static resolver doesn't support pagination
82
82
  }),
@@ -0,0 +1,19 @@
1
+ import * as R from "ramda";
2
+
3
+ export function itemLimitForData(entry = [], component) {
4
+ const itemLimitValue = Number(R.path(["rules", "item_limit"], component));
5
+
6
+ const itemLimit =
7
+ itemLimitValue && itemLimitValue > 0
8
+ ? itemLimitValue
9
+ : Number.MAX_SAFE_INTEGER;
10
+
11
+ const isInRange = (min, max) => R.both(R.gte(R.__, min), R.lt(R.__, max));
12
+
13
+ const entryShouldBeSliced = (entry) =>
14
+ isInRange(0, R.length(entry))(itemLimit);
15
+
16
+ const sliceEntriesUpToItemLimit = R.slice(0, itemLimit);
17
+
18
+ return R.when(entryShouldBeSliced, sliceEntriesUpToItemLimit)(entry);
19
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-ui-components",
3
- "version": "15.0.0-rc.99",
3
+ "version": "15.1.0-rc.2",
4
4
  "description": "Applicaster Zapp React Native ui components for the Quick Brick App",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -28,10 +28,10 @@
28
28
  },
29
29
  "homepage": "https://github.com/applicaster/quickbrick#readme",
30
30
  "dependencies": {
31
- "@applicaster/applicaster-types": "15.0.0-rc.99",
32
- "@applicaster/zapp-react-native-bridge": "15.0.0-rc.99",
33
- "@applicaster/zapp-react-native-redux": "15.0.0-rc.99",
34
- "@applicaster/zapp-react-native-utils": "15.0.0-rc.99",
31
+ "@applicaster/applicaster-types": "15.1.0-rc.2",
32
+ "@applicaster/zapp-react-native-bridge": "15.1.0-rc.2",
33
+ "@applicaster/zapp-react-native-redux": "15.1.0-rc.2",
34
+ "@applicaster/zapp-react-native-utils": "15.1.0-rc.2",
35
35
  "fast-json-stable-stringify": "^2.1.0",
36
36
  "promise": "^8.3.0",
37
37
  "url": "^0.11.0",
@@ -1,46 +0,0 @@
1
- import { PixelRatio } from "react-native";
2
- import { withAdjustedLineHeight } from "..";
3
-
4
- const FONT_SCALE = 1.118;
5
- jest.spyOn(PixelRatio, "getFontScale").mockReturnValue(FONT_SCALE);
6
-
7
- describe("withAdjustedLineHeight", () => {
8
- it("with provided fontScale and styles", () => {
9
- // setup
10
- const styles = {
11
- lineHeight: 10,
12
- };
13
-
14
- // run
15
- const result = withAdjustedLineHeight(styles);
16
-
17
- // verify
18
- expect(result).toEqual({
19
- lineHeight: styles.lineHeight * FONT_SCALE,
20
- });
21
- });
22
-
23
- it("with provided fontScale and empty styles", () => {
24
- // setup
25
- const styles = {};
26
-
27
- // run
28
- const result = withAdjustedLineHeight(styles);
29
-
30
- // verify
31
- expect(result).toEqual(styles);
32
- });
33
-
34
- it("with non-number lineHeight", () => {
35
- // setup
36
- const styles = {
37
- lineHeight: NaN,
38
- };
39
-
40
- // run
41
- const result = withAdjustedLineHeight(styles);
42
-
43
- // verify
44
- expect(result).toEqual(styles);
45
- });
46
- });
@@ -1,21 +0,0 @@
1
- import { PixelRatio } from "react-native";
2
- import { isNil, identity } from "ramda";
3
- import { toNumber } from "@applicaster/zapp-react-native-utils/numberUtils";
4
- import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
5
-
6
- // HACK - to prevent text flickering
7
- export const withAdjustedLineHeight = (styles) => {
8
- const fontScale = PixelRatio.getFontScale();
9
- const lineHeight = toNumber(styles.lineHeight);
10
-
11
- if (isNil(lineHeight)) {
12
- return styles;
13
- }
14
-
15
- return { ...styles, lineHeight: lineHeight * fontScale };
16
- };
17
-
18
- export const withScaledLineHeight = platformSelect({
19
- ios: withAdjustedLineHeight,
20
- default: identity,
21
- });
@@ -1,57 +0,0 @@
1
- import * as React from "react";
2
- import * as R from "ramda";
3
- import { Text, TextStyle, View, ViewStyle } from "react-native";
4
-
5
- import { getLocalizations } from "@applicaster/zapp-react-native-utils/localizationUtils";
6
- import { getAppStylesColor } from "@applicaster/zapp-react-native-utils/stylesUtils";
7
- import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
8
- import { styleKeys } from "@applicaster/zapp-react-native-utils/styleKeysUtils";
9
-
10
- type Props = {
11
- styles: {};
12
- error: {};
13
- remoteConfigurations: { localizations: {} };
14
- };
15
-
16
- const defaultAppStyles = {
17
- loading_error_label: {
18
- color: "#aaa",
19
- },
20
- };
21
-
22
- const textStyles = (appStyles = defaultAppStyles): TextStyle => ({
23
- color: getAppStylesColor("loading_error_label", appStyles),
24
- fontSize: 36,
25
- textAlign: "center",
26
- });
27
-
28
- const errorStyles = ({ backgroundColor }): ViewStyle => ({
29
- flex: 1,
30
- width: "100%",
31
- height: "100%",
32
- justifyContent: "center",
33
- alignItems: "center",
34
- position: "absolute",
35
- zIndex: 100,
36
- backgroundColor,
37
- });
38
-
39
- export function ErrorDisplayComponent({
40
- styles,
41
- remoteConfigurations: { localizations },
42
- }: Props) {
43
- const theme = useTheme();
44
- const backgroundColor = theme?.app_background_color;
45
-
46
- const { stream_error_message = "Cannot play stream" } = getLocalizations({
47
- localizations,
48
- });
49
-
50
- const appStyles = R.prop(styleKeys.style_namespace, styles);
51
-
52
- return (
53
- <View style={errorStyles({ backgroundColor })}>
54
- <Text style={textStyles(appStyles)}>{stream_error_message}</Text>
55
- </View>
56
- );
57
- }
@@ -1,9 +0,0 @@
1
- import * as R from "ramda";
2
-
3
- import { connectToStore } from "@applicaster/zapp-react-native-redux/utils/connectToStore";
4
-
5
- import { ErrorDisplayComponent } from "./ErrorDisplay";
6
-
7
- export const ErrorDisplay = R.compose(
8
- connectToStore(R.pick(["remoteConfigurations"]))
9
- )(ErrorDisplayComponent);
@@ -1,101 +0,0 @@
1
- import { Player } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/player";
2
- import NetInfo from "@react-native-community/netinfo";
3
-
4
- import { useEffect, useMemo, useRef, useState } from "react";
5
- import { showAlertDialog } from "@applicaster/zapp-react-native-utils/alertUtils";
6
- import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
7
- import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
8
- import { log_info } from "./logger";
9
-
10
- type RestrictMobilePlaybackProps = {
11
- player?: Player;
12
- entry?: ZappEntry;
13
- pluginConfiguration?: Record<string, string>;
14
- close: () => void;
15
- };
16
-
17
- export const useRestrictMobilePlayback = ({
18
- player,
19
- entry,
20
- pluginConfiguration,
21
- close,
22
- }: RestrictMobilePlaybackProps): { isRestricted: boolean } => {
23
- const dialogVisibleRef = useRef<boolean>(false);
24
- const theme = useTheme();
25
-
26
- useEffect(() => {
27
- return () => {
28
- if (isTV()) {
29
- return;
30
- }
31
-
32
- dialogVisibleRef.current = false;
33
- };
34
- }, []);
35
-
36
- const isConnectionRestricted = useMemo(() => {
37
- if (isTV()) {
38
- return false;
39
- }
40
-
41
- return player && entry?.extensions?.connection_restricted;
42
- }, [player, entry]);
43
-
44
- const [isRestricted, setIsRestricted] = useState<boolean>(
45
- isConnectionRestricted
46
- );
47
-
48
- useEffect(() => {
49
- if (!isConnectionRestricted) {
50
- return;
51
- }
52
-
53
- const stopPlayer = () => {
54
- log_info(
55
- "Stopping player due to mobile restriction, connection_restricted: true"
56
- );
57
-
58
- player?.close();
59
-
60
- dialogVisibleRef.current = true;
61
-
62
- showAlertDialog({
63
- title:
64
- pluginConfiguration?.mobile_connection_restricted_alert_title ||
65
- "Restricted Connection Type",
66
- message:
67
- pluginConfiguration?.mobile_connection_restricted_alert_message ||
68
- "This content can only be viewed over a Wi-Fi or LAN network.",
69
- okButtonText: theme.ok_button || "OK",
70
- completion: () => {
71
- dialogVisibleRef.current = false;
72
-
73
- close();
74
- },
75
- });
76
- };
77
-
78
- return NetInfo.addEventListener((state) => {
79
- if (state.type === "cellular") {
80
- setIsRestricted(true);
81
-
82
- if (dialogVisibleRef.current) {
83
- return;
84
- }
85
-
86
- stopPlayer();
87
- } else {
88
- setIsRestricted(false);
89
- }
90
- });
91
- }, [
92
- close,
93
- entry?.extensions?.connection_restricted,
94
- pluginConfiguration,
95
- player,
96
- theme.ok_button,
97
- isConnectionRestricted,
98
- ]);
99
-
100
- return { isRestricted };
101
- };
@@ -1,30 +0,0 @@
1
- import { toStringOrEmpty } from "..";
2
-
3
- describe("toStringOrEmpty", () => {
4
- test("returns empty string for undefined", () => {
5
- expect(toStringOrEmpty(undefined)).toBe("");
6
- });
7
-
8
- test("returns empty string for null", () => {
9
- expect(toStringOrEmpty(null)).toBe("");
10
- });
11
-
12
- test("converts number to string", () => {
13
- expect(toStringOrEmpty(0)).toBe("0");
14
- expect(toStringOrEmpty(123)).toBe("123");
15
- expect(toStringOrEmpty(-42)).toBe("-42");
16
- });
17
-
18
- test("returns string as is", () => {
19
- expect(toStringOrEmpty("hello")).toBe("hello");
20
- expect(toStringOrEmpty("")).toBe("");
21
- });
22
-
23
- test("works with numeric strings", () => {
24
- expect(toStringOrEmpty("123")).toBe("123");
25
- });
26
-
27
- test("does not throw on falsy values like 0", () => {
28
- expect(toStringOrEmpty(0)).toBe("0");
29
- });
30
- });
@@ -1,4 +0,0 @@
1
- import { isNil } from "@applicaster/zapp-react-native-utils/utils";
2
-
3
- export const toStringOrEmpty = (value: unknown): string =>
4
- isNil(value) ? "" : String(value);
@@ -1,71 +0,0 @@
1
- import * as React from "react";
2
- import { View, StyleSheet } from "react-native";
3
-
4
- import { FocusableGroup } from "@applicaster/zapp-react-native-ui-components/Components/FocusableGroup";
5
- import { riverFocusManager } from "@applicaster/zapp-react-native-utils/appUtils/RiverFocusManager";
6
-
7
- import { topMenuLayoutChange$ } from "@applicaster/zapp-react-native-tvos-app/Layout/topMenu";
8
-
9
- const styles = StyleSheet.create({
10
- flexOne: {
11
- flex: 1,
12
- },
13
- });
14
-
15
- export const withFocusableGroupForContent = (Component) => {
16
- return function WithFocusableGroupForContent(props) {
17
- const { screenId, isInsideContainer } = props;
18
-
19
- const [topMenuHeight, setTopMenuHeight] = React.useState(0);
20
-
21
- React.useEffect(() => {
22
- const subscription = topMenuLayoutChange$.subscribe((layout) => {
23
- setTopMenuHeight(layout.height);
24
- });
25
-
26
- return () => {
27
- subscription.unsubscribe();
28
- };
29
- }, []);
30
-
31
- const focusableId = React.useMemo(
32
- () =>
33
- riverFocusManager.screenFocusableGroupId({
34
- screenId,
35
- isInsideContainer,
36
- }),
37
- [screenId, isInsideContainer]
38
- );
39
-
40
- if (isInsideContainer) {
41
- return <Component {...props} />;
42
- }
43
-
44
- return (
45
- <FocusableGroup
46
- key={focusableId}
47
- id={focusableId}
48
- // The top menu is rendered in its own FocusableGroup, anchored at the top of the screen.
49
- // When the "content" FocusableGroup starts at y = 0 as well, the two groups visually overlap.
50
- // On TvOS platform this overlap can confuse the focus engine, because the focusable bounds of
51
- // the top-menu group and the content group intersect, leading to erratic navigation between
52
- // the menu and the content (e.g. unexpected jumps or focus getting "stuck").
53
- //
54
- // To avoid this, we shift the entire content FocusableGroup down by the dynamic top menu
55
- // height (marginTop: topMenuHeight). This separates the focus regions of the two groups in
56
- // focus space, so they no longer intersect.
57
- //
58
- // The inner <View> below then applies the inverse margin (marginTop: -topMenuHeight) so that
59
- // the actual visual position of the content on screen does not change; only the focusable
60
- // bounds of the outer group are offset.
61
- style={[styles.flexOne, { marginTop: topMenuHeight }]}
62
- // this group does not have parent
63
- groupId={undefined}
64
- >
65
- <View style={[styles.flexOne, { marginTop: -1 * topMenuHeight }]}>
66
- <Component {...props} groupId={focusableId} />
67
- </View>
68
- </FocusableGroup>
69
- );
70
- };
71
- };