@applicaster/zapp-react-native-ui-components 15.1.4 → 16.0.0-alpha.1042942411

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 (202) hide show
  1. package/Components/BackgroundImage/index.ts +1 -8
  2. package/Components/BaseFocusable/index.ios.ts +12 -2
  3. package/Components/Cell/FocusableWrapper.tsx +3 -0
  4. package/Components/Cell/TvOSCellComponent.tsx +6 -3
  5. package/Components/CellRendererResolver/index.ts +1 -1
  6. package/Components/Focusable/Focusable.tsx +4 -2
  7. package/Components/Focusable/FocusableTvOS.tsx +18 -1
  8. package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +1 -0
  9. package/Components/FocusableGroup/FocusableTvOS.tsx +30 -1
  10. package/Components/GeneralContentScreen/GeneralContentScreenHookAdapter.tsx +39 -0
  11. package/Components/GeneralContentScreen/__tests__/GeneralContentScreenHookAdapter.test.tsx +64 -0
  12. package/Components/GeneralContentScreen/__tests__/HookContentFocusGroup.web.test.tsx +91 -0
  13. package/Components/GeneralContentScreen/hookAdapter/__tests__/networkService.test.ts +74 -0
  14. package/Components/GeneralContentScreen/hookAdapter/__tests__/runInBackground.test.ts +139 -0
  15. package/Components/GeneralContentScreen/hookAdapter/__tests__/validationHelper.test.ts +124 -0
  16. package/Components/GeneralContentScreen/hookAdapter/logger.ts +6 -0
  17. package/Components/GeneralContentScreen/hookAdapter/networkService.ts +53 -0
  18. package/Components/GeneralContentScreen/hookAdapter/runInBackground.ts +48 -0
  19. package/Components/GeneralContentScreen/hookAdapter/validationHelper.ts +72 -0
  20. package/Components/GeneralContentScreen/hookFocus/index.tsx +13 -0
  21. package/Components/GeneralContentScreen/hookFocus/index.web.tsx +69 -0
  22. package/Components/GeneralContentScreen/index.ts +2 -0
  23. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
  24. package/Components/HandlePlayable/HandlePlayable.tsx +13 -8
  25. package/Components/Layout/TV/NavBarContainer.tsx +1 -10
  26. package/Components/Layout/TV/ScreenContainer.tsx +2 -6
  27. package/Components/Layout/TV/__tests__/__snapshots__/NavBarContainer.test.tsx.snap +7 -12
  28. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +7 -12
  29. package/Components/Layout/TV/index.tsx +3 -4
  30. package/Components/Layout/TV/index.web.tsx +3 -4
  31. package/Components/LinkHandler/LinkHandler.tsx +2 -2
  32. package/Components/MasterCell/CONFIG_BUILDER_TO_REACT_COMPONENT.md +144 -0
  33. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +27 -48
  34. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/model.test.ts +80 -0
  35. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +213 -0
  36. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/selectors.test.ts +45 -0
  37. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/style.test.ts +49 -0
  38. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +165 -0
  39. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +405 -0
  40. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/index.ts +1 -0
  41. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/model.ts +47 -0
  42. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +198 -0
  43. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/selectors.ts +26 -0
  44. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/style.ts +29 -0
  45. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/types.ts +37 -0
  46. package/Components/MasterCell/DefaultComponents/BorderContainerView/__tests__/index.test.tsx +217 -46
  47. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +50 -119
  48. package/Components/MasterCell/DefaultComponents/Button.tsx +50 -58
  49. package/Components/MasterCell/DefaultComponents/ButtonContainerView/components/HorizontalSeparator.tsx +8 -0
  50. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.android.tv.tsx +58 -0
  51. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tsx +15 -0
  52. package/Components/MasterCell/DefaultComponents/{tv/ButtonContainerView/index.tsx → ButtonContainerView/index.tv.tsx} +3 -11
  53. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.web.ts +1 -0
  54. package/Components/MasterCell/DefaultComponents/ButtonContainerView/types.ts +40 -0
  55. package/Components/MasterCell/DefaultComponents/CellBadge/__tests__/__snapshots__/index.test.tsx.snap +18 -0
  56. package/Components/MasterCell/DefaultComponents/CellBadge/__tests__/index.test.tsx +90 -0
  57. package/Components/MasterCell/DefaultComponents/CellBadge/index.tsx +13 -14
  58. package/Components/MasterCell/DefaultComponents/DataProvider/index.tsx +163 -0
  59. package/Components/MasterCell/DefaultComponents/FocusableView/index.android.tsx +2 -23
  60. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -22
  61. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +8 -2
  62. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +11 -3
  63. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +9 -1
  64. package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +15 -14
  65. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -3
  66. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +1 -2
  67. package/Components/MasterCell/DefaultComponents/PressableView.tsx +54 -0
  68. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +46 -50
  69. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +47 -39
  70. package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +7 -6
  71. package/Components/MasterCell/DefaultComponents/SecondaryImage/utils.ts +19 -6
  72. package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +15 -0
  73. package/Components/MasterCell/DefaultComponents/Text/index.tsx +45 -15
  74. package/Components/MasterCell/DefaultComponents/UIKitAdapters/Badge.tsx +49 -0
  75. package/Components/MasterCell/DefaultComponents/__tests__/ActionButton.test.tsx +122 -0
  76. package/Components/MasterCell/DefaultComponents/__tests__/Button.test.tsx +186 -0
  77. package/Components/MasterCell/DefaultComponents/__tests__/DataProvider.test.tsx +141 -0
  78. package/Components/MasterCell/DefaultComponents/__tests__/DynamicBadge.test.tsx +156 -0
  79. package/Components/MasterCell/DefaultComponents/__tests__/__snapshots__/ActionButton.test.tsx.snap +60 -0
  80. package/Components/MasterCell/DefaultComponents/__tests__/text.test.tsx +196 -67
  81. package/Components/MasterCell/DefaultComponents/index.ts +13 -3
  82. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +189 -0
  83. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +33 -0
  84. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +33 -0
  85. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +159 -0
  86. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +18 -0
  87. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabel.ts +67 -0
  88. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +39 -0
  89. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +402 -0
  90. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +141 -0
  91. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +354 -0
  92. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/helpers.ts +105 -0
  93. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +129 -0
  94. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +123 -0
  95. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +238 -0
  96. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Asset.ts +4 -18
  97. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Button.ts +24 -73
  98. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TextLabelsContainer.ts +37 -18
  99. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TvActionButton.tsx +27 -0
  100. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +89 -0
  101. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/renderedTree.test.tsx +231 -0
  102. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +47 -48
  103. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +115 -29
  104. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/insertButtonsBetweenLabels.test.ts +45 -13
  105. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +101 -144
  106. package/Components/MasterCell/MappingFunctions/index.js +3 -2
  107. package/Components/MasterCell/README.md +4 -0
  108. package/Components/MasterCell/__tests__/__snapshots__/dataAdapter.test.js.snap +24 -0
  109. package/Components/MasterCell/__tests__/configInflater.test.js +1 -0
  110. package/Components/MasterCell/__tests__/elementMapper.test.js +46 -0
  111. package/Components/MasterCell/contexts/PressedStateContext.ts +3 -0
  112. package/Components/MasterCell/dataAdapter.ts +4 -1
  113. package/Components/MasterCell/elementMapper.tsx +54 -7
  114. package/Components/MasterCell/hooks/index.ts +2 -0
  115. package/Components/MasterCell/hooks/usePressedState.ts +4 -0
  116. package/Components/MasterCell/index.tsx +20 -4
  117. package/Components/MasterCell/utils/__tests__/cloneChildrenWithIds.test.tsx +43 -0
  118. package/Components/MasterCell/utils/__tests__/useFilterChildren.test.tsx +80 -0
  119. package/Components/MasterCell/utils/index.ts +82 -16
  120. package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +33 -69
  121. package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +25 -47
  122. package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +122 -39
  123. package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +671 -0
  124. package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +257 -88
  125. package/Components/ModalComponent/AudioPlayer/Components/index.ts +6 -4
  126. package/Components/ModalComponent/BottomSheetDragArea.tsx +33 -0
  127. package/Components/ModalComponent/BottomSheetModalContent.tsx +17 -10
  128. package/Components/ModalComponent/__tests__/BottomSheetDragArea.test.tsx +74 -0
  129. package/Components/OfflineHandler/__tests__/__snapshots__/index.test.tsx.snap +4 -85
  130. package/Components/OfflineHandler/__tests__/index.test.tsx +101 -32
  131. package/Components/OfflineHandler/hooks.ts +123 -0
  132. package/Components/OfflineHandler/index.tsx +51 -73
  133. package/Components/OfflineHandler/utils/index.ts +1 -13
  134. package/Components/PlayerContainer/PlayerContainer.tsx +6 -5
  135. package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +2 -4
  136. package/Components/PreloaderWrapper/index.tsx +1 -1
  137. package/Components/River/ComponentsMap/ComponentsMap.tsx +8 -21
  138. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
  139. package/Components/River/RefreshControl.tsx +19 -105
  140. package/Components/River/River.tsx +9 -82
  141. package/Components/River/TV/River.tsx +31 -14
  142. package/Components/River/TV/index.tsx +8 -4
  143. package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +30 -0
  144. package/Components/River/TV/utils/index.ts +4 -0
  145. package/Components/River/TV/withFocusableGroupForContent.tsx +71 -0
  146. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +4 -0
  147. package/Components/River/__tests__/componentsMap.test.js +38 -0
  148. package/Components/River/hooks/__tests__/usePullToRefresh.test.ts +132 -0
  149. package/Components/River/hooks/index.ts +1 -0
  150. package/Components/River/hooks/usePullToRefresh.ts +51 -0
  151. package/Components/Screen/TV/index.web.tsx +4 -2
  152. package/Components/Screen/__tests__/Screen.test.tsx +65 -42
  153. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +68 -44
  154. package/Components/Screen/hooks.ts +2 -3
  155. package/Components/Screen/index.tsx +2 -3
  156. package/Components/Screen/orientationHandler.ts +3 -3
  157. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +5 -3
  158. package/Components/ScreenResolver/index.tsx +9 -5
  159. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +1 -1
  160. package/Components/ScreenRevealManager/ScreenRevealManager.ts +40 -8
  161. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +86 -69
  162. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +1 -1
  163. package/Components/Tabs/TabContent.tsx +7 -4
  164. package/Components/TopCutoffOverlay/__tests__/TopCutoffOverlay.test.tsx +201 -0
  165. package/Components/TopCutoffOverlay/hooks/__tests__/useMarginTop.test.ts +130 -0
  166. package/Components/TopCutoffOverlay/hooks/index.ts +1 -0
  167. package/Components/TopCutoffOverlay/hooks/useMarginTop.ts +59 -0
  168. package/Components/TopCutoffOverlay/index.tsx +55 -0
  169. package/Components/Transitioner/index.js +3 -3
  170. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +5 -5
  171. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +15 -7
  172. package/Components/VideoModal/utils.ts +12 -10
  173. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
  174. package/Components/Viewport/ViewportAware/index.tsx +16 -7
  175. package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
  176. package/Components/default-cell-renderer/viewTrees/mobile/index.ts +0 -3
  177. package/Contexts/CachedDimensionsContext/__tests__/index.test.ts +154 -0
  178. package/Contexts/ScreenContext/index.tsx +25 -18
  179. package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
  180. package/Decorators/Analytics/index.tsx +6 -5
  181. package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +1 -0
  182. package/Decorators/ConfigurationWrapper/const.ts +1 -0
  183. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
  184. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
  185. package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
  186. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
  187. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
  188. package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +80 -0
  189. package/Helpers/DataSourceHelper/index.ts +19 -0
  190. package/package.json +5 -5
  191. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +0 -135
  192. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/types.ts +0 -25
  193. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +0 -112
  194. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +0 -107
  195. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +0 -153
  196. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +0 -67
  197. package/Components/OfflineHandler/NotificationView/utils.ts +0 -34
  198. package/Components/River/TV/withTVEventHandler.tsx +0 -36
  199. package/Helpers/DataSourceHelper/index.js +0 -19
  200. /package/Components/BackgroundImage/{BackgroundImage.tv.android.tsx → BackgroundImage.android.tv.tsx} +0 -0
  201. /package/Components/BackgroundImage/{BackgroundImage.tv.ios.tsx → BackgroundImage.ios.tv.tsx} +0 -0
  202. /package/Components/BackgroundImage/{BackgroundImage.tv.web.tsx → BackgroundImage.tsx} +0 -0
@@ -1,88 +1,7 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
- exports[`OfflineHandler renders 1`] = `
4
- <View
5
- style={
6
- {
7
- "flex": 1,
8
- "position": "relative",
9
- }
10
- }
11
- >
12
- <View
13
- style={
14
- {
15
- "bottom": 0,
16
- "left": 0,
17
- "position": "absolute",
18
- "right": 0,
19
- "top": 0,
20
- }
21
- }
22
- >
23
- <View
24
- accessibilityState={
25
- {
26
- "busy": undefined,
27
- "checked": undefined,
28
- "disabled": undefined,
29
- "expanded": undefined,
30
- "selected": undefined,
31
- }
32
- }
33
- accessible={true}
34
- collapsable={false}
35
- focusable={true}
36
- onClick={[Function]}
37
- onResponderGrant={[Function]}
38
- onResponderMove={[Function]}
39
- onResponderRelease={[Function]}
40
- onResponderTerminate={[Function]}
41
- onResponderTerminationRequest={[Function]}
42
- onStartShouldSetResponder={[Function]}
43
- style={
44
- {
45
- "alignItems": "center",
46
- "backgroundColor": undefined,
47
- "flex": 1,
48
- "height": 88,
49
- "justifyContent": "center",
50
- "left": 0,
51
- "opacity": 0,
52
- "position": "absolute",
53
- "right": 0,
54
- "top": 0,
55
- "transform": [
56
- {
57
- "translateY": -88,
58
- },
59
- ],
60
- "width": "100%",
61
- }
62
- }
63
- >
64
- <View
65
- style={
66
- {
67
- "backgroundColor": undefined,
68
- "height": 44,
69
- }
70
- }
71
- />
72
- <Text
73
- style={
74
- {
75
- "color": undefined,
76
- "fontFamily": undefined,
77
- "fontSize": undefined,
78
- "letterSpacing": undefined,
79
- "lineHeight": undefined,
80
- }
81
- }
82
- >
83
- You are back online
84
- </Text>
85
- </View>
86
- </View>
87
- </View>
3
+ exports[`OfflineHandler renders its children without wrapping them in a notification view 1`] = `
4
+ <Text>
5
+ child content
6
+ </Text>
88
7
  `;
@@ -1,43 +1,52 @@
1
1
  import React from "react";
2
+ import { Text } from "react-native";
2
3
  import { render } from "@testing-library/react-native";
4
+ import { subscriberFor } from "@applicaster/zapp-react-native-utils/reactHooks/useSubscriberFor";
3
5
 
4
6
  jest.useFakeTimers({ legacyFakeTimers: true });
5
7
 
8
+ // `isOnline` (@applicaster/quick-brick-core/App/NetworkStatusProvider/utils)
9
+ // only treats connectionInfo as "ready" when it has NetInfoState-shaped
10
+ // isConnected/isInternetReachable/details fields, so the mock has to mirror
11
+ // that shape for online/offline to actually be distinguishable.
6
12
  let mockConnectionStatus = false;
7
- const mockPreviousValue = null;
8
13
 
9
14
  jest.mock("@applicaster/zapp-react-native-utils/reactHooks/connection", () => {
10
15
  return {
11
- useConnectionInfo: jest.fn(() => mockConnectionStatus),
16
+ ...jest.requireActual(
17
+ "@applicaster/zapp-react-native-utils/reactHooks/connection"
18
+ ),
19
+ useConnectionInfo: jest.fn(() => ({
20
+ details: {},
21
+ isConnected: mockConnectionStatus,
22
+ isInternetReachable: mockConnectionStatus,
23
+ })),
12
24
  };
13
25
  });
14
26
 
15
- jest.mock("@applicaster/zapp-react-native-utils/reactHooks/utils", () => {
16
- return {
17
- usePrevious: jest.fn(() => mockPreviousValue),
18
- };
19
- });
27
+ const mock_storeObj = {
28
+ plugins: [
29
+ {
30
+ name: "offline experience",
31
+ identifier: "offline-experience",
32
+ type: "general",
33
+ module: {
34
+ useOfflineExperienceConfiguration: () => ({
35
+ configurationFields: {},
36
+ localizations: {
37
+ offline_toast_message: "No internet connection",
38
+ online_toast_message: "You are back online",
39
+ },
40
+ }),
41
+ },
42
+ },
43
+ ],
44
+ };
20
45
 
21
46
  jest.mock("@applicaster/zapp-react-native-redux/hooks", () => ({
22
47
  ...jest.requireActual("@applicaster/zapp-react-native-redux/hooks"),
23
- usePickFromState: () => ({
24
- plugins: [
25
- {
26
- name: "offline experience",
27
- identifier: "offline-experience",
28
- type: "general",
29
- module: {
30
- useOfflineExperienceConfiguration: () => ({
31
- configurationFields: {},
32
- localizations: {
33
- offline_toast_message: "No internet connection",
34
- online_toast_message: "You are back online",
35
- },
36
- }),
37
- },
38
- },
39
- ],
40
- }),
48
+ usePlugins: () => mock_storeObj.plugins,
49
+ usePickFromState: () => ({}),
41
50
  }));
42
51
 
43
52
  jest.mock("react-native-safe-area-context", () => ({
@@ -45,21 +54,81 @@ jest.mock("react-native-safe-area-context", () => ({
45
54
  }));
46
55
 
47
56
  describe("OfflineHandler", () => {
48
- const { OfflineHandler, NotificationView } = require("../");
57
+ const { OfflineHandler } = require("../");
58
+
59
+ beforeEach(() => {
60
+ mockConnectionStatus = false;
61
+ });
62
+
63
+ it("renders its children without wrapping them in a notification view", () => {
64
+ const { toJSON } = render(
65
+ <OfflineHandler>
66
+ <Text>child content</Text>
67
+ </OfflineHandler>
68
+ );
49
69
 
50
- it("renders", () => {
51
- const { toJSON } = render(<OfflineHandler />);
52
70
  expect(toJSON()).toMatchSnapshot();
53
71
  });
54
72
 
55
- it("renders Notification mode if component was rendered while online", () => {
73
+ it("emits a showToast event on mount when the device starts offline", () => {
74
+ mockConnectionStatus = false;
75
+
76
+ const handleShowToast = jest.fn();
77
+ const unsubscribe = subscriberFor("showToast", handleShowToast);
78
+
79
+ render(<OfflineHandler />);
80
+
81
+ expect(handleShowToast).toHaveBeenCalledWith(
82
+ expect.objectContaining({
83
+ id: "offline-status",
84
+ message: "No internet connection",
85
+ timeout: 5000,
86
+ }),
87
+ // the shared event bus appends a dispose fn as the last handler arg
88
+ expect.any(Function)
89
+ );
90
+
91
+ unsubscribe();
92
+ });
93
+
94
+ it("does not emit a showToast event on mount when the device starts online", () => {
56
95
  mockConnectionStatus = true;
57
96
 
58
- const { rerender, UNSAFE_getByType } = render(<OfflineHandler />);
97
+ const handleShowToast = jest.fn();
98
+ const unsubscribe = subscriberFor("showToast", handleShowToast);
99
+
100
+ render(<OfflineHandler />);
101
+
102
+ expect(handleShowToast).not.toHaveBeenCalled();
103
+
104
+ unsubscribe();
105
+ });
106
+
107
+ it("emits a showToast event when the device comes back online", () => {
108
+ mockConnectionStatus = false;
109
+
110
+ const handleShowToast = jest.fn();
111
+ const unsubscribe = subscriberFor("showToast", handleShowToast);
112
+
113
+ const { rerender } = render(<OfflineHandler />);
59
114
 
115
+ // ignore the offline toast emitted on mount; we only care about the
116
+ // offline -> online transition here
117
+ handleShowToast.mockClear();
118
+
119
+ mockConnectionStatus = true;
60
120
  rerender(<OfflineHandler />);
61
121
 
62
- const notificationsView = UNSAFE_getByType(NotificationView);
63
- expect(notificationsView).toBeDefined();
122
+ expect(handleShowToast).toHaveBeenCalledWith(
123
+ expect.objectContaining({
124
+ id: "offline-status",
125
+ message: "You are back online",
126
+ timeout: 2000,
127
+ }),
128
+ // the shared event bus appends a dispose fn as the last handler arg
129
+ expect.any(Function)
130
+ );
131
+
132
+ unsubscribe();
64
133
  });
65
134
  });
@@ -0,0 +1,123 @@
1
+ import React, { useMemo } from "react";
2
+
3
+ import {
4
+ isWeb,
5
+ platformSelect,
6
+ } from "@applicaster/zapp-react-native-utils/reactUtils";
7
+ import { sessionStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/SessionStorage";
8
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
9
+
10
+ import { usePlugins } from "@applicaster/zapp-react-native-redux";
11
+ import { textTransform } from "@applicaster/zapp-react-native-utils/cellUtils";
12
+ import { findPluginByIdentifier } from "@applicaster/zapp-react-native-utils/pluginUtils";
13
+
14
+ import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
15
+
16
+ const OFFLINE_EXPERIENCE = "offline-experience";
17
+
18
+ const NETWORK_STATUS_LOCALIZATIONS_KEY = "network_status_localizations";
19
+
20
+ const THEME_STORAGE_NAMESPACE = "quick-brick-theme";
21
+
22
+ export const useNotificationHeight = () => {
23
+ const insets = useSafeAreaInsets();
24
+
25
+ const navBarHeight = platformSelect({ ios: 44, android: 56 });
26
+
27
+ const statusHeight = insets.top;
28
+ const notificationHeight = statusHeight + navBarHeight;
29
+
30
+ return { statusHeight, notificationHeight };
31
+ };
32
+
33
+ export function useNetworkStatusLocalizations() {
34
+ const [storedLocalizations, setStoredLocalizations] = React.useState<Record<
35
+ string,
36
+ string
37
+ > | null>(null);
38
+
39
+ React.useEffect(() => {
40
+ async function loadStoredLocalizations() {
41
+ try {
42
+ const stored = await sessionStorage.getItem(
43
+ NETWORK_STATUS_LOCALIZATIONS_KEY,
44
+ THEME_STORAGE_NAMESPACE
45
+ );
46
+
47
+ if (stored) {
48
+ setStoredLocalizations(stored);
49
+ }
50
+ } catch (error) {
51
+ console.error("Error loading network status localizations", error);
52
+ }
53
+ }
54
+
55
+ loadStoredLocalizations();
56
+ }, []);
57
+
58
+ return storedLocalizations;
59
+ }
60
+
61
+ export function useOfflineHandlerProps(online: boolean) {
62
+ const theme = useTheme<BaseThemePropertiesTV>();
63
+ const storedLocalizations = useNetworkStatusLocalizations();
64
+ const plugins = usePlugins();
65
+ const offlinePlugin = findPluginByIdentifier(OFFLINE_EXPERIENCE, plugins);
66
+ const { useOfflineExperienceConfiguration } = offlinePlugin?.module ?? {};
67
+
68
+ const {
69
+ configurationFields,
70
+ localizations,
71
+ }: {
72
+ configurationFields: OfflineExperiencePluginConfiguration;
73
+ localizations: OfflineExperienceLocalizations;
74
+ } = useOfflineExperienceConfiguration?.() ?? {};
75
+
76
+ let title, subtitle, style;
77
+
78
+ if (isWeb()) {
79
+ title = online
80
+ ? (theme?.online_notification_title ??
81
+ storedLocalizations?.online_notification_title)
82
+ : (theme?.offline_notification_title ??
83
+ storedLocalizations?.offline_notification_title);
84
+
85
+ subtitle = online
86
+ ? (theme?.online_notification_subtitle ??
87
+ storedLocalizations?.online_notification_subtitle)
88
+ : (theme?.offline_notification_subtitle ??
89
+ storedLocalizations?.offline_notification_subtitle);
90
+ } else {
91
+ title = textTransform(
92
+ configurationFields.offline_toast_message_text_transform,
93
+ online
94
+ ? localizations.online_toast_message
95
+ : localizations.offline_toast_message
96
+ );
97
+
98
+ style = {
99
+ color: online
100
+ ? configurationFields.offline_toast_message_online_font_color
101
+ : configurationFields.offline_toast_message_font_color,
102
+ backgroundColor: online
103
+ ? configurationFields.offline_toast_online_background_color
104
+ : configurationFields.offline_toast_offline_background_color,
105
+ fontFamily: platformSelect({
106
+ ios: configurationFields.offline_toast_message_ios_font_family,
107
+ android: configurationFields.offline_toast_message_android_font_family,
108
+ }),
109
+ fontSize: configurationFields.offline_toast_message_font_size,
110
+ lineHeight: configurationFields.offline_toast_message_line_height,
111
+ letterSpacing: platformSelect({
112
+ ios: configurationFields.offline_toast_message_letter_spacing_ios,
113
+ android:
114
+ configurationFields.offline_toast_message_letter_spacing_android,
115
+ }),
116
+ };
117
+ }
118
+
119
+ return useMemo(
120
+ () => ({ message: title, extraMessage: subtitle, style }),
121
+ [title, subtitle, style]
122
+ );
123
+ }
@@ -1,105 +1,83 @@
1
- import React, { useCallback, useEffect, useRef, useState } from "react";
2
- import { View, StyleSheet } from "react-native";
3
- import { NotificationView as NotificationViewNative } from "./NotificationView/NotificationView";
4
- import { NotificationView as NotificationViewSamsung } from "./NotificationView/NotificationView.samsung";
5
- import { NotificationView as NotificationViewLG } from "./NotificationView/NotificationView.lg";
6
-
1
+ import React, { useCallback, useEffect } from "react";
7
2
  import { useConnectionInfo } from "@applicaster/zapp-react-native-utils/reactHooks/connection";
8
3
  import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
9
- import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
4
+ import { useSubscriberFor } from "@applicaster/zapp-react-native-utils/reactHooks/useSubscriberFor";
10
5
 
6
+ import { useOfflineHandlerProps } from "./hooks";
11
7
  import { componentsLogger } from "../../Helpers/logger";
12
8
  import { parseConnectionInfo } from "./utils";
13
9
 
14
10
  const logger = componentsLogger.addSubsystem("OfflineHandler");
15
11
 
12
+ // mirrors NOTIFICATION_TOAST_EVENTS.SHOW_TOAST in
13
+ // @applicaster/quick-brick-core/App/NotificationToastRenderer/NotificationToastManager
14
+ // (kept as a literal here since this package must not depend on quick-brick-core)
15
+ const SHOW_TOAST_EVENT = "showToast";
16
+ const OFFLINE_STATUS_TOAST_ID = "offline-status";
17
+
16
18
  type Props = {
17
19
  children?: React.ReactElement<any>;
18
20
  };
19
21
 
20
- const styles = StyleSheet.create({
21
- container: {
22
- flex: 1,
23
- position: "relative",
24
- },
25
- });
26
-
27
22
  const timers = {
28
23
  goingOffline: 5000,
29
24
  goingOnline: 2000,
30
25
  };
31
26
 
32
- export const NotificationView = platformSelect({
33
- samsung_tv: NotificationViewSamsung,
34
- lg_tv: NotificationViewLG,
35
- default: NotificationViewNative,
36
- });
37
-
38
27
  export const OfflineHandler = ({ children }: Props) => {
39
28
  const connectionInfo = useConnectionInfo(false);
40
- const { online, deviceOnline } = parseConnectionInfo(connectionInfo);
29
+ const { online } = parseConnectionInfo(connectionInfo);
30
+ const prevOnline = usePrevious(online);
41
31
 
42
- const [hidden, setHidden] = useState<boolean>(true);
43
- const previousOnline: boolean = usePrevious(online);
44
- const timer = useRef<NodeJS.Timeout | null>(null);
45
- const initialRender = useRef(false);
32
+ const emitShowToast = useSubscriberFor(SHOW_TOAST_EVENT);
46
33
 
47
- const dismiss = useCallback(() => {
48
- setHidden(true);
34
+ const { message, extraMessage, style } = useOfflineHandlerProps(online);
49
35
 
50
- if (timer.current) {
51
- clearTimeout(timer.current);
52
- timer.current = null;
53
- }
54
- }, []);
36
+ const showOnlineToast = useCallback(() => {
37
+ logger.log("Device is back online, emit toast");
55
38
 
56
- const setTimer = useCallback((cb, duration) => {
57
- if (timer.current) {
58
- clearTimeout(timer.current);
59
- }
39
+ emitShowToast({
40
+ id: OFFLINE_STATUS_TOAST_ID,
41
+ message,
42
+ extraMessage,
43
+ style,
44
+ timeout: timers.goingOnline,
45
+ });
46
+ }, [emitShowToast, message, extraMessage, style]);
47
+
48
+ const showOfflineToast = useCallback(() => {
49
+ logger.log("Device went offline, emit toast");
60
50
 
61
- timer.current = setTimeout(cb, duration);
62
- }, []);
51
+ emitShowToast({
52
+ id: OFFLINE_STATUS_TOAST_ID,
53
+ message,
54
+ extraMessage,
55
+ style,
56
+ timeout: timers.goingOffline,
57
+ });
58
+ }, [emitShowToast, message, extraMessage, style]);
63
59
 
64
60
  useEffect(() => {
65
- if (initialRender.current) {
66
- const wentOffline = previousOnline && online === false;
67
- const wentOnline = !previousOnline && online;
68
- const renderedOffline = !online && previousOnline === null;
69
-
70
- if (!deviceOnline || wentOffline || renderedOffline) {
71
- logger.log("Device went offline");
72
- setTimer(() => setHidden(true), timers.goingOffline);
73
- setHidden(false);
61
+ // On mount there is no previous state yet: only surface the offline toast
62
+ // for a device that starts offline. Don't announce "back online" for a
63
+ // device that was online to begin with.
64
+ if (prevOnline === undefined) {
65
+ if (!online) {
66
+ showOfflineToast();
74
67
  }
75
68
 
76
- if (wentOnline) {
77
- logger.log("Device is back online");
78
- setHidden(false);
79
- setTimer(() => setHidden(true), timers.goingOnline);
80
- }
81
- } else {
82
- initialRender.current = true;
69
+ return;
83
70
  }
84
71
 
85
- return () => {
86
- if (timer.current) {
87
- clearTimeout(timer.current);
88
- timer.current = null;
72
+ // On subsequent renders, only emit a toast when the online/offline state changes.
73
+ if (prevOnline !== online) {
74
+ if (!online) {
75
+ showOfflineToast();
76
+ } else {
77
+ showOnlineToast();
89
78
  }
90
- };
91
- }, [connectionInfo]);
92
-
93
- return (
94
- <View style={styles.container}>
95
- <NotificationView
96
- hidden={hidden}
97
- online={online}
98
- previousOnline={previousOnline}
99
- dismiss={dismiss}
100
- >
101
- {children}
102
- </NotificationView>
103
- </View>
104
- );
79
+ }
80
+ }, [online, prevOnline, showOfflineToast, showOnlineToast]);
81
+
82
+ return children;
105
83
  };
@@ -1,9 +1,8 @@
1
1
  import * as R from "ramda";
2
-
3
2
  import { NetInfoState } from "@react-native-community/netinfo";
4
3
  import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
5
4
  import { isOnline as isOnlineUtils } from "@applicaster/quick-brick-core/App/NetworkStatusProvider/utils";
6
- import { useSafeAreaInsets } from "react-native-safe-area-context";
5
+
7
6
  import { Platform } from "react-native";
8
7
 
9
8
  type ConnectionInfo = NetInfoState & { deviceStatus: NetStatus };
@@ -51,14 +50,3 @@ export const parseConnectionInfo: typeof parseConnectionInfoForDefault =
51
50
  samsung_tv: parseConnectionInfoForSamsung,
52
51
  default: parseConnectionInfoForDefault,
53
52
  });
54
-
55
- export const useNotificationHeight = () => {
56
- const insets = useSafeAreaInsets();
57
-
58
- const navBarHeight = platformSelect({ ios: 44, android: 56 });
59
-
60
- const statusHeight = insets.top;
61
- const notificationHeight = statusHeight + navBarHeight;
62
-
63
- return { statusHeight, notificationHeight };
64
- };
@@ -17,7 +17,7 @@ import {
17
17
 
18
18
  import { TVEventHandlerComponent } from "@applicaster/zapp-react-native-tvos-ui-components/Components/TVEventHandlerComponent";
19
19
  import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
20
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
20
+
21
21
  import {
22
22
  useBackHandler,
23
23
  useNavigation,
@@ -59,6 +59,7 @@ import {
59
59
  PlayerNativeCommandTypes,
60
60
  PlayerNativeSendCommand,
61
61
  } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/playerNativeCommand";
62
+ import { useAppData } from "@applicaster/zapp-react-native-redux";
62
63
 
63
64
  type Props = {
64
65
  Player: React.ComponentType<any>;
@@ -237,7 +238,7 @@ const PlayerContainerComponent = (props: Props) => {
237
238
  const [isLoadingNextVideo, setIsLoadingNextVideo] = React.useState(false);
238
239
 
239
240
  const navigator = useNavigation();
240
- const { appData } = usePickFromState(["appData"]);
241
+ const { isTabletPortrait } = useAppData();
241
242
  const prevItemId = usePrevious(item?.id);
242
243
  const screenData = useTargetScreenData(item);
243
244
  const { setVisible: showNavBar } = useSetNavbarState();
@@ -658,7 +659,7 @@ const PlayerContainerComponent = (props: Props) => {
658
659
  <PlayerFocusableWrapperView
659
660
  nextFocusDown={context.bottomFocusableId}
660
661
  >
661
- {Player ? (
662
+ {!Player ? null : (
662
663
  <Player
663
664
  source={{
664
665
  uri,
@@ -673,7 +674,7 @@ const PlayerContainerComponent = (props: Props) => {
673
674
  fullscreen={mode === VideoModalMode.FULLSCREEN}
674
675
  inline={inline}
675
676
  isModal={isModal}
676
- isTabletPortrait={appData.isTabletPortrait}
677
+ isTabletPortrait={isTabletPortrait}
677
678
  muted={false}
678
679
  playableItem={item}
679
680
  playerEvent={playerEvent}
@@ -689,7 +690,7 @@ const PlayerContainerComponent = (props: Props) => {
689
690
  >
690
691
  {renderApplePlayer(applePlayerProps)}
691
692
  </Player>
692
- ) : null}
693
+ )}
693
694
  </PlayerFocusableWrapperView>
694
695
  </View>
695
696
  {/* Components container */}
@@ -183,10 +183,8 @@ jest.mock("../../GeneralContentScreen", () => ({
183
183
  GeneralContentScreen: () => null,
184
184
  }));
185
185
 
186
- jest.mock("@applicaster/zapp-react-native-redux/hooks", () => ({
187
- usePickFromState: jest.fn(() => ({
188
- appData: { isTabletPortrait: false },
189
- })),
186
+ jest.mock("@applicaster/zapp-react-native-redux", () => ({
187
+ useAppData: jest.fn(() => ({ isTabletPortrait: false })),
190
188
  }));
191
189
 
192
190
  import { PlayerContainer, VideoModalMode } from "../PlayerContainer";
@@ -2,7 +2,7 @@ import React from "react";
2
2
 
3
3
  type PreloaderWrapperProps = {
4
4
  showPreloader?: boolean;
5
- children?: React.ReactNode;
5
+ children: React.ReactElement | null;
6
6
  };
7
7
 
8
8
  export const PreloaderWrapper: React.FC<PreloaderWrapperProps> = ({