@applicaster/zapp-react-native-ui-components 16.0.0-rc.12 → 16.0.0-rc.121
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.
- package/Components/AudioPlayer/tv/__tests__/audioPlayer.test.js +19 -0
- package/Components/AudioPlayer/tv/__tests__/index.test.tsx +120 -0
- package/Components/AudioPlayer/tv/index.tsx +5 -1
- package/Components/Cell/Cell.tsx +3 -6
- package/Components/Cell/TvOSCellComponent.tsx +0 -1
- package/Components/CellRendererResolver/index.ts +1 -1
- package/Components/FocusableGroup/FocusableTvOS.tsx +11 -7
- package/Components/GeneralContentScreen/GeneralContentScreenHookAdapter.tsx +54 -0
- package/Components/GeneralContentScreen/__tests__/GeneralContentScreenHookAdapter.test.tsx +64 -0
- package/Components/GeneralContentScreen/__tests__/HookContentFocusGroup.web.test.tsx +91 -0
- package/Components/GeneralContentScreen/hookAdapter/__tests__/networkService.test.ts +74 -0
- package/Components/GeneralContentScreen/hookAdapter/__tests__/runInBackground.test.ts +139 -0
- package/Components/GeneralContentScreen/hookAdapter/__tests__/skipExpression.test.ts +294 -0
- package/Components/GeneralContentScreen/hookAdapter/__tests__/validationHelper.test.ts +188 -0
- package/Components/GeneralContentScreen/hookAdapter/logger.ts +6 -0
- package/Components/GeneralContentScreen/hookAdapter/networkService.ts +53 -0
- package/Components/GeneralContentScreen/hookAdapter/runInBackground.ts +48 -0
- package/Components/GeneralContentScreen/hookAdapter/skipExpression.ts +252 -0
- package/Components/GeneralContentScreen/hookAdapter/validationHelper.ts +90 -0
- package/Components/GeneralContentScreen/hookFocus/index.tsx +13 -0
- package/Components/GeneralContentScreen/hookFocus/index.web.tsx +69 -0
- package/Components/GeneralContentScreen/index.ts +2 -0
- package/Components/Layout/FullWidthRow.tsx +38 -0
- package/Components/LinkHandler/LinkHandler.tsx +2 -7
- package/Components/MasterCell/DefaultComponents/ActionButton.tsx +5 -1
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +47 -21
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +19 -4
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +43 -1
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +34 -6
- package/Components/MasterCell/DefaultComponents/Button.tsx +1 -0
- package/Components/MasterCell/DefaultComponents/Image/hoc/utils/__tests__/withDimensions.test.ts +420 -125
- package/Components/MasterCell/DefaultComponents/Image/hoc/utils/index.ts +114 -19
- package/Components/MasterCell/DefaultComponents/Image/hoc/withDimensions.tsx +19 -8
- package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -3
- package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +2 -4
- package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +14 -6
- package/Components/MasterCell/DefaultComponents/PressableView.tsx +29 -9
- package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +4 -0
- package/Components/MasterCell/DefaultComponents/Toggle.tsx +36 -14
- package/Components/MasterCell/DefaultComponents/__tests__/Toggle.test.tsx +167 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +154 -100
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +15 -4
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +56 -22
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +6 -4
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +3 -1
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +34 -21
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +15 -4
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +23 -16
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +8 -3
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +2 -2
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/insertButtonsBetweenLabels.test.ts +45 -13
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +2 -2
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/__tests__/factory.test.ts +32 -0
- package/Components/MasterCell/__tests__/masterCell.test.js +60 -0
- package/Components/MasterCell/contexts/PressedStateContext.ts +3 -0
- package/Components/MasterCell/dataAdapter.ts +15 -5
- package/Components/MasterCell/hooks/index.ts +2 -0
- package/Components/MasterCell/hooks/usePressedState.ts +4 -0
- package/Components/MasterCell/index.tsx +57 -8
- package/Components/MasterCell/utils/__tests__/resolveColor.test.js +169 -132
- package/Components/MasterCell/utils/__tests__/resolveColorForProp.test.js +92 -0
- package/Components/MasterCell/utils/__tests__/withoutFocusedState.test.js +20 -0
- package/Components/MasterCell/utils/index.ts +103 -46
- package/Components/ModalComponent/ActionsBottomSheet/ActionButton.tsx +56 -0
- package/Components/ModalComponent/ActionsBottomSheet/__tests__/openActionsBottomSheet.test.ts +38 -0
- package/Components/ModalComponent/ActionsBottomSheet/boundActionButton.tsx +24 -0
- package/Components/ModalComponent/ActionsBottomSheet/index.ts +9 -0
- package/Components/ModalComponent/ActionsBottomSheet/openActionsBottomSheet.ts +61 -0
- package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +392 -0
- package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +772 -0
- package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +728 -0
- package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +1028 -0
- package/Components/ModalComponent/AudioPlayer/Components/NowPlayingHeaderSection.tsx +205 -0
- package/Components/ModalComponent/AudioPlayer/Components/__tests__/Item.test.tsx +164 -0
- package/Components/ModalComponent/AudioPlayer/Components/__tests__/NowPlayingHeaderSection.test.tsx +36 -0
- package/Components/ModalComponent/AudioPlayer/Components/index.ts +9 -0
- package/Components/ModalComponent/AudioPlayer/utils/__tests__/mergeStyles.test.ts +433 -0
- package/Components/ModalComponent/AudioPlayer/utils/mergeStyles.ts +563 -0
- package/Components/ModalComponent/BottomSheetDragArea.tsx +33 -0
- package/Components/ModalComponent/BottomSheetModalContent.tsx +440 -50
- package/Components/ModalComponent/Button/index.tsx +5 -3
- package/Components/ModalComponent/Header/CloseButton.tsx +12 -0
- package/Components/ModalComponent/Header/__tests__/CloseButton.test.tsx +72 -0
- package/Components/ModalComponent/Header/index.tsx +20 -7
- package/Components/ModalComponent/Header/utils.ts +1 -1
- package/Components/ModalComponent/ModalBlocksStyleContext.tsx +10 -0
- package/Components/ModalComponent/SortableList.tsx +156 -0
- package/Components/ModalComponent/SortableList.web.tsx +34 -0
- package/Components/ModalComponent/TextInputContent.tsx +135 -0
- package/Components/ModalComponent/__tests__/BottomSheetDragArea.test.tsx +74 -0
- package/Components/ModalComponent/__tests__/BottomSheetModalContent.test.tsx +343 -0
- package/Components/ModalComponent/__tests__/SortableList.web.test.tsx +16 -0
- package/Components/ModalComponent/__tests__/TextInputContent.test.tsx +169 -0
- package/Components/ModalComponent/__tests__/showTextInput.test.ts +120 -0
- package/Components/ModalComponent/index.tsx +18 -9
- package/Components/ModalComponent/presets/__tests__/getCell.test.ts +90 -0
- package/Components/ModalComponent/presets/dynamicCollectionCell.tsx +78 -0
- package/Components/ModalComponent/presets/index.ts +27 -0
- package/Components/ModalComponent/presets/selectableCell.tsx +28 -0
- package/Components/ModalComponent/presets/standardCell.tsx +41 -0
- package/Components/ModalComponent/presets/types.ts +35 -0
- package/Components/ModalComponent/showTextInput.ts +54 -0
- package/Components/ModalComponent/utils.ts +82 -21
- package/Components/OfflineHandler/__tests__/__snapshots__/index.test.tsx.snap +4 -85
- package/Components/OfflineHandler/__tests__/index.test.tsx +186 -17
- package/Components/OfflineHandler/hooks.ts +111 -0
- package/Components/OfflineHandler/index.tsx +91 -75
- package/Components/OfflineHandler/utils/index.ts +1 -13
- package/Components/PlayerContainer/PlayerContainer.tsx +28 -11
- package/Components/PlayerContainer/ProgramInfo/index.tsx +3 -4
- package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +337 -0
- package/Components/PreloaderWrapper/index.tsx +1 -1
- package/Components/River/ComponentsMap/ComponentsMap.tsx +91 -62
- package/Components/River/River.tsx +4 -0
- package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +142 -89
- package/Components/River/__tests__/componentsMap.test.js +8 -3
- package/Components/River/__tests__/resultCallback.test.tsx +47 -0
- package/Components/Screen/__tests__/Screen.test.tsx +24 -0
- package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +2 -0
- package/Components/Screen/index.tsx +13 -4
- package/Components/Screen/navigationHandler.ts +8 -8
- package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +5 -3
- package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +1 -1
- package/Components/ScreenRevealManager/ScreenRevealManager.ts +18 -2
- package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +36 -0
- package/Components/ScreenRevealManager/__tests__/withScreenRevealManager.test.tsx +56 -0
- package/Components/ScreenRevealManager/withScreenRevealManager.tsx +21 -4
- package/Components/TopCutoffOverlay/index.tsx +2 -2
- package/Components/Transitioner/AnimationManager.js +1 -1
- package/Components/Transitioner/Scene.tsx +4 -3
- package/Components/Transitioner/Transitioner.tsx +98 -49
- package/Components/Transitioner/__tests__/transitioner.test.js +372 -3
- package/Components/VideoLive/LiveImageManager.ts +9 -3
- package/Components/VideoLive/__tests__/LiveImageManager.test.ts +59 -0
- package/Components/VideoModal/PlayerWrapper.tsx +4 -1
- package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -0
- package/Components/VideoModal/__tests__/playerWrapperStyle.test.ts +58 -0
- package/Components/VideoModal/__tests__/playerWrapperUtils.test.ts +16 -0
- package/Components/VideoModal/playerWrapperStyle.ts +21 -3
- package/Components/VideoModal/playerWrapperUtils.ts +7 -1
- package/Components/ZappFrameworkComponents/BarView/BarView.tsx +12 -5
- package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +2 -2
- package/Contexts/CachedDimensionsContext/__tests__/index.test.ts +154 -0
- package/Contexts/CachedDimensionsContext/index.ts +17 -2
- package/Contexts/ModalNavigationContext/__tests__/index.test.tsx +15 -0
- package/Contexts/ModalNavigationContext/index.tsx +2 -2
- package/Decorators/ZappPipesDataConnector/__tests__/screenToolbox.test.ts +347 -0
- package/Decorators/ZappPipesDataConnector/__tests__/screenToolboxUnavailable.test.ts +53 -0
- package/Decorators/ZappPipesDataConnector/index.tsx +19 -4
- package/Decorators/ZappPipesDataConnector/screenToolbox.ts +384 -0
- package/Helpers/ComponentCellSelectionHelper/index.js +0 -6
- package/Helpers/index.js +0 -39
- package/Hooks/useEntryActionsExpander.ts +63 -0
- package/assets/empty-Info.png +0 -0
- package/package.json +6 -5
- package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +0 -112
- package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +0 -107
- package/Components/OfflineHandler/NotificationView/NotificationView.tsx +0 -153
- package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +0 -66
- package/Components/OfflineHandler/NotificationView/utils.ts +0 -34
- package/Components/Screen/utils.ts +0 -16
- package/Helpers/Analytics/index.js +0 -95
- /package/Components/BackgroundImage/{BackgroundImage.tv.android.tsx → BackgroundImage.android.tv.tsx} +0 -0
- /package/Components/BackgroundImage/{BackgroundImage.tv.ios.tsx → BackgroundImage.ios.tv.tsx} +0 -0
- /package/Components/MasterCell/DefaultComponents/ButtonContainerView/{index.tv.android.tsx → index.android.tv.tsx} +0 -0
- /package/Decorators/ZappPipesDataConnector/__tests__/{Hero.js → Hero.tsx} +0 -0
|
@@ -1,26 +1,49 @@
|
|
|
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
|
-
|
|
13
|
+
let mockStillOfflineAfterResumeCount = 0;
|
|
8
14
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
const mockComponentsLogger = {
|
|
16
|
+
log: jest.fn(),
|
|
17
|
+
debug: jest.fn(),
|
|
18
|
+
info: jest.fn(),
|
|
19
|
+
warning: jest.fn(),
|
|
20
|
+
error: jest.fn(),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
jest.mock("../../../Helpers/logger", () => {
|
|
24
|
+
const proxy = {
|
|
25
|
+
log: (event) => mockComponentsLogger.log(event),
|
|
26
|
+
debug: (event) => mockComponentsLogger.debug(event),
|
|
27
|
+
info: (event) => mockComponentsLogger.info(event),
|
|
28
|
+
warning: (event) => mockComponentsLogger.warning(event),
|
|
29
|
+
error: (event) => mockComponentsLogger.error(event),
|
|
30
|
+
addSubsystem: () => proxy,
|
|
15
31
|
};
|
|
32
|
+
|
|
33
|
+
return { componentsLogger: proxy };
|
|
16
34
|
});
|
|
17
35
|
|
|
18
|
-
jest.mock("@applicaster/zapp-react-native-utils/reactHooks/
|
|
36
|
+
jest.mock("@applicaster/zapp-react-native-utils/reactHooks/connection", () => {
|
|
19
37
|
return {
|
|
20
38
|
...jest.requireActual(
|
|
21
|
-
"@applicaster/zapp-react-native-utils/reactHooks/
|
|
39
|
+
"@applicaster/zapp-react-native-utils/reactHooks/connection"
|
|
22
40
|
),
|
|
23
|
-
|
|
41
|
+
useConnectionInfo: jest.fn(() => ({
|
|
42
|
+
details: {},
|
|
43
|
+
isConnected: mockConnectionStatus,
|
|
44
|
+
isInternetReachable: mockConnectionStatus,
|
|
45
|
+
stillOfflineAfterResumeCount: mockStillOfflineAfterResumeCount,
|
|
46
|
+
})),
|
|
24
47
|
};
|
|
25
48
|
});
|
|
26
49
|
|
|
@@ -54,21 +77,167 @@ jest.mock("react-native-safe-area-context", () => ({
|
|
|
54
77
|
}));
|
|
55
78
|
|
|
56
79
|
describe("OfflineHandler", () => {
|
|
57
|
-
const { OfflineHandler
|
|
80
|
+
const { OfflineHandler } = require("../");
|
|
81
|
+
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
mockConnectionStatus = false;
|
|
84
|
+
mockStillOfflineAfterResumeCount = 0;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("renders its children without wrapping them in a notification view", () => {
|
|
88
|
+
const { toJSON } = render(
|
|
89
|
+
<OfflineHandler>
|
|
90
|
+
<Text>child content</Text>
|
|
91
|
+
</OfflineHandler>
|
|
92
|
+
);
|
|
58
93
|
|
|
59
|
-
it("renders", () => {
|
|
60
|
-
const { toJSON } = render(<OfflineHandler />);
|
|
61
94
|
expect(toJSON()).toMatchSnapshot();
|
|
62
95
|
});
|
|
63
96
|
|
|
64
|
-
it("
|
|
97
|
+
it("emits a showToast event on mount when the device starts offline", () => {
|
|
98
|
+
mockConnectionStatus = false;
|
|
99
|
+
|
|
100
|
+
const handleShowToast = jest.fn();
|
|
101
|
+
const unsubscribe = subscriberFor("showToast", handleShowToast);
|
|
102
|
+
|
|
103
|
+
render(<OfflineHandler />);
|
|
104
|
+
|
|
105
|
+
expect(handleShowToast).toHaveBeenCalledWith(
|
|
106
|
+
expect.objectContaining({
|
|
107
|
+
id: "offline-status",
|
|
108
|
+
message: "No internet connection",
|
|
109
|
+
timeout: 5000,
|
|
110
|
+
}),
|
|
111
|
+
// the shared event bus appends a dispose fn as the last handler arg
|
|
112
|
+
expect.any(Function)
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
unsubscribe();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("does not emit a showToast event on mount when the device starts online", () => {
|
|
65
119
|
mockConnectionStatus = true;
|
|
66
120
|
|
|
67
|
-
const
|
|
121
|
+
const handleShowToast = jest.fn();
|
|
122
|
+
const unsubscribe = subscriberFor("showToast", handleShowToast);
|
|
123
|
+
|
|
124
|
+
render(<OfflineHandler />);
|
|
125
|
+
|
|
126
|
+
expect(handleShowToast).not.toHaveBeenCalled();
|
|
127
|
+
|
|
128
|
+
unsubscribe();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("emits a showToast event when the device comes back online", () => {
|
|
132
|
+
mockConnectionStatus = false;
|
|
133
|
+
|
|
134
|
+
const handleShowToast = jest.fn();
|
|
135
|
+
const unsubscribe = subscriberFor("showToast", handleShowToast);
|
|
68
136
|
|
|
137
|
+
const { rerender } = render(<OfflineHandler />);
|
|
138
|
+
|
|
139
|
+
// ignore the offline toast emitted on mount; we only care about the
|
|
140
|
+
// offline -> online transition here
|
|
141
|
+
handleShowToast.mockClear();
|
|
142
|
+
|
|
143
|
+
mockConnectionStatus = true;
|
|
69
144
|
rerender(<OfflineHandler />);
|
|
70
145
|
|
|
71
|
-
|
|
72
|
-
|
|
146
|
+
expect(handleShowToast).toHaveBeenCalledWith(
|
|
147
|
+
expect.objectContaining({
|
|
148
|
+
id: "offline-status",
|
|
149
|
+
message: "You are back online",
|
|
150
|
+
timeout: 2000,
|
|
151
|
+
}),
|
|
152
|
+
// the shared event bus appends a dispose fn as the last handler arg
|
|
153
|
+
expect.any(Function)
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
unsubscribe();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("re-shows the offline toast when still offline after app resume", () => {
|
|
160
|
+
mockConnectionStatus = false;
|
|
161
|
+
|
|
162
|
+
const handleShowToast = jest.fn();
|
|
163
|
+
const unsubscribe = subscriberFor("showToast", handleShowToast);
|
|
164
|
+
|
|
165
|
+
const { rerender } = render(<OfflineHandler />);
|
|
166
|
+
handleShowToast.mockClear();
|
|
167
|
+
|
|
168
|
+
mockStillOfflineAfterResumeCount = 1;
|
|
169
|
+
rerender(<OfflineHandler />);
|
|
170
|
+
|
|
171
|
+
expect(handleShowToast).toHaveBeenCalledTimes(1);
|
|
172
|
+
|
|
173
|
+
expect(handleShowToast).toHaveBeenCalledWith(
|
|
174
|
+
expect.objectContaining({
|
|
175
|
+
id: "offline-status",
|
|
176
|
+
message: "No internet connection",
|
|
177
|
+
timeout: 5000,
|
|
178
|
+
}),
|
|
179
|
+
expect.any(Function)
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// further renders with the same counter must not re-emit
|
|
183
|
+
rerender(<OfflineHandler />);
|
|
184
|
+
expect(handleShowToast).toHaveBeenCalledTimes(1);
|
|
185
|
+
|
|
186
|
+
unsubscribe();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("does not re-show the offline toast for a counter bump while online", () => {
|
|
190
|
+
mockConnectionStatus = true;
|
|
191
|
+
|
|
192
|
+
const handleShowToast = jest.fn();
|
|
193
|
+
const unsubscribe = subscriberFor("showToast", handleShowToast);
|
|
194
|
+
|
|
195
|
+
const { rerender } = render(<OfflineHandler />);
|
|
196
|
+
|
|
197
|
+
mockStillOfflineAfterResumeCount = 1;
|
|
198
|
+
rerender(<OfflineHandler />);
|
|
199
|
+
|
|
200
|
+
expect(handleShowToast).not.toHaveBeenCalled();
|
|
201
|
+
|
|
202
|
+
unsubscribe();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("does not emit on mount just because the counter is already > 0", () => {
|
|
206
|
+
mockConnectionStatus = true;
|
|
207
|
+
mockStillOfflineAfterResumeCount = 3;
|
|
208
|
+
|
|
209
|
+
const handleShowToast = jest.fn();
|
|
210
|
+
const unsubscribe = subscriberFor("showToast", handleShowToast);
|
|
211
|
+
|
|
212
|
+
render(<OfflineHandler />);
|
|
213
|
+
|
|
214
|
+
expect(handleShowToast).not.toHaveBeenCalled();
|
|
215
|
+
|
|
216
|
+
unsubscribe();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("logs toast emissions at info level", () => {
|
|
220
|
+
mockConnectionStatus = false;
|
|
221
|
+
mockComponentsLogger.info.mockClear();
|
|
222
|
+
mockComponentsLogger.warning.mockClear();
|
|
223
|
+
|
|
224
|
+
const { rerender } = render(<OfflineHandler />);
|
|
225
|
+
|
|
226
|
+
mockConnectionStatus = true;
|
|
227
|
+
rerender(<OfflineHandler />);
|
|
228
|
+
|
|
229
|
+
expect(mockComponentsLogger.info).toHaveBeenCalledWith(
|
|
230
|
+
expect.objectContaining({
|
|
231
|
+
message: "OfflineHandler: device is offline, emit toast",
|
|
232
|
+
})
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
expect(mockComponentsLogger.info).toHaveBeenCalledWith(
|
|
236
|
+
expect.objectContaining({
|
|
237
|
+
message: "OfflineHandler: device is back online, emit toast",
|
|
238
|
+
})
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
expect(mockComponentsLogger.warning).not.toHaveBeenCalled();
|
|
73
242
|
});
|
|
74
243
|
});
|
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
|
|
9
|
+
import { usePlugins } from "@applicaster/zapp-react-native-redux";
|
|
10
|
+
import { textTransform } from "@applicaster/zapp-react-native-utils/cellUtils";
|
|
11
|
+
import { findPluginByIdentifier } from "@applicaster/zapp-react-native-utils/pluginUtils";
|
|
12
|
+
|
|
13
|
+
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
14
|
+
|
|
15
|
+
const OFFLINE_EXPERIENCE = "offline-experience";
|
|
16
|
+
|
|
17
|
+
const NETWORK_STATUS_LOCALIZATIONS_KEY = "network_status_localizations";
|
|
18
|
+
|
|
19
|
+
const THEME_STORAGE_NAMESPACE = "quick-brick-theme";
|
|
20
|
+
|
|
21
|
+
export function useNetworkStatusLocalizations() {
|
|
22
|
+
const [storedLocalizations, setStoredLocalizations] = React.useState<Record<
|
|
23
|
+
string,
|
|
24
|
+
string
|
|
25
|
+
> | null>(null);
|
|
26
|
+
|
|
27
|
+
React.useEffect(() => {
|
|
28
|
+
async function loadStoredLocalizations() {
|
|
29
|
+
try {
|
|
30
|
+
const stored = await sessionStorage.getItem(
|
|
31
|
+
NETWORK_STATUS_LOCALIZATIONS_KEY,
|
|
32
|
+
THEME_STORAGE_NAMESPACE
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (stored) {
|
|
36
|
+
setStoredLocalizations(stored);
|
|
37
|
+
}
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error("Error loading network status localizations", error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
loadStoredLocalizations();
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
return storedLocalizations;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function useOfflineHandlerProps(online: boolean) {
|
|
50
|
+
const theme = useTheme<BaseThemePropertiesTV>();
|
|
51
|
+
const storedLocalizations = useNetworkStatusLocalizations();
|
|
52
|
+
const plugins = usePlugins();
|
|
53
|
+
const offlinePlugin = findPluginByIdentifier(OFFLINE_EXPERIENCE, plugins);
|
|
54
|
+
const { useOfflineExperienceConfiguration } = offlinePlugin?.module ?? {};
|
|
55
|
+
|
|
56
|
+
const {
|
|
57
|
+
configurationFields,
|
|
58
|
+
localizations,
|
|
59
|
+
}: {
|
|
60
|
+
configurationFields: OfflineExperiencePluginConfiguration;
|
|
61
|
+
localizations: OfflineExperienceLocalizations;
|
|
62
|
+
} = useOfflineExperienceConfiguration?.() ?? {};
|
|
63
|
+
|
|
64
|
+
let title, subtitle, style;
|
|
65
|
+
|
|
66
|
+
if (isWeb()) {
|
|
67
|
+
title = online
|
|
68
|
+
? (theme?.online_notification_title ??
|
|
69
|
+
storedLocalizations?.online_notification_title)
|
|
70
|
+
: (theme?.offline_notification_title ??
|
|
71
|
+
storedLocalizations?.offline_notification_title);
|
|
72
|
+
|
|
73
|
+
subtitle = online
|
|
74
|
+
? (theme?.online_notification_subtitle ??
|
|
75
|
+
storedLocalizations?.online_notification_subtitle)
|
|
76
|
+
: (theme?.offline_notification_subtitle ??
|
|
77
|
+
storedLocalizations?.offline_notification_subtitle);
|
|
78
|
+
} else {
|
|
79
|
+
title = textTransform(
|
|
80
|
+
configurationFields.offline_toast_message_text_transform,
|
|
81
|
+
online
|
|
82
|
+
? localizations.online_toast_message
|
|
83
|
+
: localizations.offline_toast_message
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
style = {
|
|
87
|
+
color: online
|
|
88
|
+
? configurationFields.offline_toast_message_online_font_color
|
|
89
|
+
: configurationFields.offline_toast_message_font_color,
|
|
90
|
+
backgroundColor: online
|
|
91
|
+
? configurationFields.offline_toast_online_background_color
|
|
92
|
+
: configurationFields.offline_toast_offline_background_color,
|
|
93
|
+
fontFamily: platformSelect({
|
|
94
|
+
ios: configurationFields.offline_toast_message_ios_font_family,
|
|
95
|
+
android: configurationFields.offline_toast_message_android_font_family,
|
|
96
|
+
}),
|
|
97
|
+
fontSize: configurationFields.offline_toast_message_font_size,
|
|
98
|
+
lineHeight: configurationFields.offline_toast_message_line_height,
|
|
99
|
+
letterSpacing: platformSelect({
|
|
100
|
+
ios: configurationFields.offline_toast_message_letter_spacing_ios,
|
|
101
|
+
android:
|
|
102
|
+
configurationFields.offline_toast_message_letter_spacing_android,
|
|
103
|
+
}),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return useMemo(
|
|
108
|
+
() => ({ message: title, extraMessage: subtitle, style }),
|
|
109
|
+
[title, subtitle, style]
|
|
110
|
+
);
|
|
111
|
+
}
|
|
@@ -1,105 +1,121 @@
|
|
|
1
|
-
import React, { useCallback, useEffect
|
|
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 {
|
|
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
|
|
29
|
+
const { online } = parseConnectionInfo(connectionInfo);
|
|
30
|
+
const prevOnline = usePrevious(online);
|
|
41
31
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
const timer = useRef<NodeJS.Timeout | null>(null);
|
|
45
|
-
const initialRender = useRef(false);
|
|
32
|
+
const stillOfflineAfterResumeCount =
|
|
33
|
+
connectionInfo?.stillOfflineAfterResumeCount ?? 0;
|
|
46
34
|
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (timer.current) {
|
|
51
|
-
clearTimeout(timer.current);
|
|
52
|
-
timer.current = null;
|
|
53
|
-
}
|
|
54
|
-
}, []);
|
|
55
|
-
|
|
56
|
-
const setTimer = useCallback((cb, duration) => {
|
|
57
|
-
if (timer.current) {
|
|
58
|
-
clearTimeout(timer.current);
|
|
59
|
-
}
|
|
35
|
+
const prevStillOfflineAfterResumeCount = usePrevious(
|
|
36
|
+
stillOfflineAfterResumeCount
|
|
37
|
+
);
|
|
60
38
|
|
|
61
|
-
|
|
62
|
-
|
|
39
|
+
const emitShowToast = useSubscriberFor(SHOW_TOAST_EVENT);
|
|
40
|
+
|
|
41
|
+
const { message, extraMessage, style } = useOfflineHandlerProps(online);
|
|
42
|
+
|
|
43
|
+
const showOnlineToast = useCallback(() => {
|
|
44
|
+
logger.info({
|
|
45
|
+
message: "OfflineHandler: device is back online, emit toast",
|
|
46
|
+
data: { online, prevOnline },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
emitShowToast({
|
|
50
|
+
id: OFFLINE_STATUS_TOAST_ID,
|
|
51
|
+
message,
|
|
52
|
+
extraMessage,
|
|
53
|
+
style,
|
|
54
|
+
timeout: timers.goingOnline,
|
|
55
|
+
});
|
|
56
|
+
}, [emitShowToast, message, extraMessage, style, online, prevOnline]);
|
|
57
|
+
|
|
58
|
+
const showOfflineToast = useCallback(() => {
|
|
59
|
+
logger.info({
|
|
60
|
+
message: "OfflineHandler: device is offline, emit toast",
|
|
61
|
+
data: { online, prevOnline, stillOfflineAfterResumeCount },
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
emitShowToast({
|
|
65
|
+
id: OFFLINE_STATUS_TOAST_ID,
|
|
66
|
+
message,
|
|
67
|
+
extraMessage,
|
|
68
|
+
style,
|
|
69
|
+
timeout: timers.goingOffline,
|
|
70
|
+
});
|
|
71
|
+
}, [
|
|
72
|
+
emitShowToast,
|
|
73
|
+
message,
|
|
74
|
+
extraMessage,
|
|
75
|
+
style,
|
|
76
|
+
online,
|
|
77
|
+
prevOnline,
|
|
78
|
+
stillOfflineAfterResumeCount,
|
|
79
|
+
]);
|
|
63
80
|
|
|
64
81
|
useEffect(() => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
logger.log("Device went offline");
|
|
72
|
-
setTimer(() => setHidden(true), timers.goingOffline);
|
|
73
|
-
setHidden(false);
|
|
82
|
+
// On mount there is no previous state yet: only surface the offline toast
|
|
83
|
+
// for a device that starts offline. Don't announce "back online" for a
|
|
84
|
+
// device that was online to begin with.
|
|
85
|
+
if (prevOnline === undefined) {
|
|
86
|
+
if (!online) {
|
|
87
|
+
showOfflineToast();
|
|
74
88
|
}
|
|
75
89
|
|
|
76
|
-
|
|
77
|
-
logger.log("Device is back online");
|
|
78
|
-
setHidden(false);
|
|
79
|
-
setTimer(() => setHidden(true), timers.goingOnline);
|
|
80
|
-
}
|
|
81
|
-
} else {
|
|
82
|
-
initialRender.current = true;
|
|
90
|
+
return;
|
|
83
91
|
}
|
|
84
92
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
93
|
+
// On subsequent renders, only emit a toast when the online/offline state changes.
|
|
94
|
+
if (prevOnline !== online) {
|
|
95
|
+
if (!online) {
|
|
96
|
+
showOfflineToast();
|
|
97
|
+
} else {
|
|
98
|
+
showOnlineToast();
|
|
89
99
|
}
|
|
90
|
-
}
|
|
91
|
-
}, [
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
}
|
|
101
|
+
}, [online, prevOnline, showOfflineToast, showOnlineToast]);
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
// NetworkStatusProvider bumps this counter when the app resumed while offline
|
|
105
|
+
// and is still offline after the verification delay: remind the user.
|
|
106
|
+
if (
|
|
107
|
+
prevStillOfflineAfterResumeCount !== undefined &&
|
|
108
|
+
stillOfflineAfterResumeCount > prevStillOfflineAfterResumeCount &&
|
|
109
|
+
!online
|
|
110
|
+
) {
|
|
111
|
+
showOfflineToast();
|
|
112
|
+
}
|
|
113
|
+
}, [
|
|
114
|
+
stillOfflineAfterResumeCount,
|
|
115
|
+
prevStillOfflineAfterResumeCount,
|
|
116
|
+
online,
|
|
117
|
+
showOfflineToast,
|
|
118
|
+
]);
|
|
119
|
+
|
|
120
|
+
return children;
|
|
105
121
|
};
|
|
@@ -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
|
-
|
|
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
|
-
};
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
platformSelect,
|
|
10
10
|
isAndroidTVPlatform,
|
|
11
11
|
isTvOSPlatform,
|
|
12
|
+
isApplePlatform,
|
|
12
13
|
} from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
13
14
|
import {
|
|
14
15
|
isAudioItem,
|
|
@@ -60,6 +61,7 @@ import {
|
|
|
60
61
|
PlayerNativeSendCommand,
|
|
61
62
|
} from "@applicaster/zapp-react-native-utils/appUtils/playerManager/playerNativeCommand";
|
|
62
63
|
import { useAppData } from "@applicaster/zapp-react-native-redux";
|
|
64
|
+
import { PLAYER_EVENTS } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/const";
|
|
63
65
|
|
|
64
66
|
type Props = {
|
|
65
67
|
Player: React.ComponentType<any>;
|
|
@@ -188,12 +190,10 @@ const renderApplePlayer = ({
|
|
|
188
190
|
return null;
|
|
189
191
|
}
|
|
190
192
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
+
// Title/Subtitle are not resolved here: `ProgramInfo` reads them from the
|
|
194
|
+
// player manager's content channel via `usePlayerContent`.
|
|
193
195
|
return (
|
|
194
196
|
<ProgramInfo
|
|
195
|
-
title={title}
|
|
196
|
-
subtitle={summary}
|
|
197
197
|
plugin_configuration={pluginConfiguration}
|
|
198
198
|
videoStyle={videoStyle}
|
|
199
199
|
/>
|
|
@@ -282,12 +282,16 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
log_debug(
|
|
285
|
-
`onPlayNextPerformNextVideoPlay: delay completed, replaceTop to item: ${playNextOverlayState.entry.title}, id: ${playNextOverlayState.entry.id}`
|
|
285
|
+
`onPlayNextPerformNextVideoPlay: delay completed, replaceTop to item: ${playNextOverlayState.entry.title}, id: ${playNextOverlayState.entry.id}, outgoing item id: ${item?.id}`
|
|
286
286
|
);
|
|
287
287
|
|
|
288
|
+
playNextOverlayState.hidePlayNext();
|
|
289
|
+
|
|
290
|
+
playerEvent(PLAYER_EVENTS.PlayNextTriggered, { id: item?.id });
|
|
291
|
+
|
|
288
292
|
setIsLoadingNextVideo(true);
|
|
289
293
|
playEntry(playNextOverlayState.entry);
|
|
290
|
-
}, [playNextOverlayState]);
|
|
294
|
+
}, [playNextOverlayState, item?.id]);
|
|
291
295
|
|
|
292
296
|
const playNextData: Nullable<PlayNextData> = React.useMemo(() => {
|
|
293
297
|
if (!playNextOverlayState) {
|
|
@@ -344,11 +348,15 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
344
348
|
setIsLoadingNextVideo(false);
|
|
345
349
|
|
|
346
350
|
const resumeTime = Number(item?.extensions?.resumeTime);
|
|
351
|
+
const activePlayer = playerManager.getActivePlayer();
|
|
347
352
|
|
|
348
|
-
// Сhecking that the player itself knows where to start playing, and there is no need to call seekTo after returning from the Сhromecast
|
|
353
|
+
// Сhecking that the player itself knows where to start playing, and there is no need to call seekTo after returning from the Сhromecast.
|
|
354
|
+
// Live streams and a missing/zero resume position are skipped: seeking to 0 on
|
|
355
|
+
// a live stream pulls playback off the live edge into a buffer-starvation seek loop.
|
|
349
356
|
if (
|
|
350
|
-
!
|
|
351
|
-
|
|
357
|
+
!activePlayer?.hasResumePosition() &&
|
|
358
|
+
resumeTime > 0 &&
|
|
359
|
+
!activePlayer?.isLive()
|
|
352
360
|
) {
|
|
353
361
|
player?.seekTo(resumeTime);
|
|
354
362
|
}
|
|
@@ -396,10 +404,17 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
396
404
|
const { eventType } = event;
|
|
397
405
|
|
|
398
406
|
if (!isLanguageOverlayVisible && eventType === "menu") {
|
|
407
|
+
// First Back dismisses Play Next, the next one closes the player
|
|
408
|
+
if (playNextOverlayState) {
|
|
409
|
+
playNextOverlayState.handleUserCancelPlayNext();
|
|
410
|
+
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
399
414
|
close();
|
|
400
415
|
}
|
|
401
416
|
},
|
|
402
|
-
[close]
|
|
417
|
+
[close, playNextOverlayState]
|
|
403
418
|
);
|
|
404
419
|
|
|
405
420
|
// Effects
|
|
@@ -466,6 +481,8 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
466
481
|
if (isModal && mode === VideoModalMode.MAXIMIZED) {
|
|
467
482
|
if (disableMiniPlayer) {
|
|
468
483
|
navigator.closeVideoModal();
|
|
484
|
+
} else {
|
|
485
|
+
navigator.minimiseVideoModal();
|
|
469
486
|
}
|
|
470
487
|
}
|
|
471
488
|
|
|
@@ -489,7 +506,7 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
489
506
|
const isAudioContent = isAudioItem(item);
|
|
490
507
|
|
|
491
508
|
useEffect(() => {
|
|
492
|
-
if (!isAudioContent) {
|
|
509
|
+
if (!isAudioContent && isApplePlatform()) {
|
|
493
510
|
sendQuickBrickEvent(
|
|
494
511
|
QUICK_BRICK_EVENTS.IDLE_TIMER_DISABLED as QuickBrickEvent,
|
|
495
512
|
{
|