@applicaster/zapp-react-native-ui-components 16.0.0-rc.8 → 16.0.0-rc.81
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 +2 -2
- package/Components/ComponentResolver/__tests__/componentResolver.test.js +1 -1
- package/Components/FocusableGroup/FocusableTvOS.tsx +11 -7
- package/Components/GeneralContentScreen/GeneralContentScreenHookAdapter.tsx +39 -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/MasterCell/DefaultComponents/ActionButton.tsx +16 -5
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +47 -21
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +34 -6
- 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/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 +32 -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/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 +30 -5
- package/Components/MasterCell/utils/__tests__/resolveColor.test.js +169 -132
- package/Components/MasterCell/utils/__tests__/resolveColorForProp.test.js +92 -0
- package/Components/MasterCell/utils/index.ts +85 -46
- package/Components/ModalComponent/ActionsBottomSheet/ActionButton.tsx +54 -0
- package/Components/ModalComponent/ActionsBottomSheet/boundActionButton.tsx +21 -0
- package/Components/ModalComponent/ActionsBottomSheet/index.ts +9 -0
- package/Components/ModalComponent/ActionsBottomSheet/openActionsBottomSheet.ts +58 -0
- package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +314 -0
- package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +407 -0
- package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +763 -0
- package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +653 -0
- package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +960 -0
- package/Components/ModalComponent/AudioPlayer/Components/NowPlayingHeaderSection.tsx +89 -0
- package/Components/ModalComponent/AudioPlayer/Components/index.ts +11 -0
- package/Components/ModalComponent/AudioPlayer/utils/__tests__/mergeStyles.test.ts +230 -0
- package/Components/ModalComponent/AudioPlayer/utils/mergeStyles.ts +332 -0
- package/Components/ModalComponent/BottomSheetDragArea.tsx +33 -0
- package/Components/ModalComponent/BottomSheetModalContent.tsx +383 -42
- package/Components/ModalComponent/Button/index.tsx +5 -3
- 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 +142 -0
- package/Components/ModalComponent/SortableList.web.tsx +34 -0
- package/Components/ModalComponent/TextInputContent.tsx +100 -0
- package/Components/ModalComponent/__tests__/BottomSheetDragArea.test.tsx +74 -0
- package/Components/ModalComponent/__tests__/BottomSheetModalContent.test.tsx +238 -0
- package/Components/ModalComponent/__tests__/SortableList.web.test.tsx +16 -0
- package/Components/ModalComponent/__tests__/TextInputContent.test.tsx +120 -0
- package/Components/ModalComponent/__tests__/showTextInput.test.ts +95 -0
- package/Components/ModalComponent/index.tsx +11 -0
- package/Components/ModalComponent/presets/__tests__/getCell.test.ts +66 -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 +43 -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 +78 -18
- package/Components/OfflineHandler/hooks.ts +111 -0
- package/Components/OfflineHandler/index.tsx +51 -73
- package/Components/OfflineHandler/utils/index.ts +1 -13
- package/Components/PlayerContainer/PlayerContainer.tsx +25 -17
- 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 +2 -2
- package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +3 -15
- package/Components/Screen/index.tsx +8 -4
- 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 +20 -3
- package/Components/TopCutoffOverlay/index.tsx +2 -2
- package/Components/Transitioner/Transitioner.tsx +28 -17
- package/Components/VideoLive/LiveImageManager.ts +1 -1
- package/Components/VideoModal/utils.ts +6 -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/Helpers/ComponentCellSelectionHelper/index.js +0 -6
- package/Helpers/index.js +0 -39
- package/Hooks/useEntryActionsExpander.ts +63 -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,4 +1,8 @@
|
|
|
1
1
|
import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
2
|
+
import {
|
|
3
|
+
toNumberWithDefault,
|
|
4
|
+
toNumberWithDefaultZero,
|
|
5
|
+
} from "@applicaster/zapp-react-native-utils/numberUtils";
|
|
2
6
|
import { PluginConfiguration } from "./";
|
|
3
7
|
import { ItemIconProps } from "./Button/ItemIcon";
|
|
4
8
|
import { ItemLabelProps } from "./Button/ItemLabel";
|
|
@@ -6,7 +10,8 @@ import { ItemProps } from "./Button/Item";
|
|
|
6
10
|
|
|
7
11
|
export function defaultItemProps(
|
|
8
12
|
config: PluginConfiguration,
|
|
9
|
-
maxWidth: number
|
|
13
|
+
maxWidth: number,
|
|
14
|
+
blockItemStyle?: Record<string, any>
|
|
10
15
|
): ItemProps {
|
|
11
16
|
const {
|
|
12
17
|
modal_bottom_sheet_item_background_color,
|
|
@@ -24,19 +29,36 @@ export function defaultItemProps(
|
|
|
24
29
|
modal_bottom_sheet_item_padding_top,
|
|
25
30
|
} = config;
|
|
26
31
|
|
|
32
|
+
const bgDefault = blockItemStyle?.background?.default;
|
|
33
|
+
|
|
34
|
+
const bgFocused =
|
|
35
|
+
blockItemStyle?.background?.focused || blockItemStyle?.background?.focus;
|
|
36
|
+
|
|
27
37
|
return {
|
|
28
|
-
backgroundColor:
|
|
29
|
-
|
|
38
|
+
backgroundColor:
|
|
39
|
+
bgDefault?.backgroundColor || modal_bottom_sheet_item_background_color,
|
|
40
|
+
focusedBackgroundColor:
|
|
41
|
+
bgFocused?.backgroundColor ||
|
|
42
|
+
modal_bottom_sheet_item_focus_background_color,
|
|
30
43
|
selectedBackgroundColor: modal_bottom_sheet_item_selected_background_color,
|
|
31
44
|
focusedSelectedBackgroundColor:
|
|
32
45
|
modal_bottom_sheet_item_focused_selected_background_color,
|
|
33
|
-
borderRadius:
|
|
46
|
+
borderRadius: toNumberWithDefault(
|
|
47
|
+
modal_bottom_sheet_item_corner_radius,
|
|
48
|
+
bgDefault?.borderRadius
|
|
49
|
+
),
|
|
34
50
|
marginBottom: modal_bottom_sheet_item_margin_bottom,
|
|
35
51
|
marginTop: modal_bottom_sheet_item_margin_top,
|
|
36
52
|
marginLeft: modal_bottom_sheet_item_margin_left,
|
|
37
53
|
marginRight: modal_bottom_sheet_item_margin_right,
|
|
38
|
-
paddingTop:
|
|
39
|
-
|
|
54
|
+
paddingTop: toNumberWithDefault(
|
|
55
|
+
modal_bottom_sheet_item_padding_top,
|
|
56
|
+
bgDefault?.paddingTop
|
|
57
|
+
),
|
|
58
|
+
paddingBottom: toNumberWithDefault(
|
|
59
|
+
modal_bottom_sheet_item_padding_bottom,
|
|
60
|
+
bgDefault?.paddingBottom
|
|
61
|
+
),
|
|
40
62
|
paddingRight: modal_bottom_sheet_item_padding_right,
|
|
41
63
|
paddingLeft: modal_bottom_sheet_item_padding_left,
|
|
42
64
|
maxWidth,
|
|
@@ -45,7 +67,8 @@ export function defaultItemProps(
|
|
|
45
67
|
|
|
46
68
|
export function defaultItemLabelProps(
|
|
47
69
|
config: PluginConfiguration,
|
|
48
|
-
sheetWidth: number
|
|
70
|
+
sheetWidth: number,
|
|
71
|
+
blockItemStyle?: Record<string, any>
|
|
49
72
|
): ItemLabelProps {
|
|
50
73
|
const {
|
|
51
74
|
modal_bottom_sheet_item_label_android_letter_spacing,
|
|
@@ -72,15 +95,48 @@ export function defaultItemLabelProps(
|
|
|
72
95
|
modal_bottom_sheet_item_padding_right,
|
|
73
96
|
} = config;
|
|
74
97
|
|
|
75
|
-
const
|
|
98
|
+
const iconWidth = toNumberWithDefaultZero(modal_bottom_sheet_item_icon_width);
|
|
99
|
+
|
|
100
|
+
const iconMarginLeft = toNumberWithDefaultZero(
|
|
101
|
+
modal_bottom_sheet_item_icon_margin_left
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const iconMarginRight = toNumberWithDefaultZero(
|
|
105
|
+
modal_bottom_sheet_item_icon_margin_right
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const itemMarginLeft = toNumberWithDefaultZero(
|
|
109
|
+
modal_bottom_sheet_item_margin_left
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const itemMarginRight = toNumberWithDefaultZero(
|
|
113
|
+
modal_bottom_sheet_item_margin_right
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const itemPaddingLeft = toNumberWithDefaultZero(
|
|
117
|
+
modal_bottom_sheet_item_padding_left
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const itemPaddingRight = toNumberWithDefaultZero(
|
|
121
|
+
modal_bottom_sheet_item_padding_right
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const calculatedMaxWidth =
|
|
76
125
|
sheetWidth -
|
|
77
|
-
(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
126
|
+
(iconWidth +
|
|
127
|
+
iconMarginLeft +
|
|
128
|
+
iconMarginRight +
|
|
129
|
+
itemMarginLeft +
|
|
130
|
+
itemMarginRight +
|
|
131
|
+
itemPaddingLeft +
|
|
132
|
+
itemPaddingRight);
|
|
133
|
+
|
|
134
|
+
const maxWidth =
|
|
135
|
+
Number.isNaN(calculatedMaxWidth) || calculatedMaxWidth <= 0
|
|
136
|
+
? sheetWidth
|
|
137
|
+
: calculatedMaxWidth;
|
|
138
|
+
|
|
139
|
+
const label1Default = blockItemStyle?.textLabel1?.default;
|
|
84
140
|
|
|
85
141
|
return {
|
|
86
142
|
fontFamily: platformSelect({
|
|
@@ -91,8 +147,11 @@ export function defaultItemLabelProps(
|
|
|
91
147
|
ios: modal_bottom_sheet_item_label_ios_letter_spacing,
|
|
92
148
|
android: modal_bottom_sheet_item_label_android_letter_spacing,
|
|
93
149
|
}),
|
|
94
|
-
fontSize:
|
|
95
|
-
|
|
150
|
+
fontSize: toNumberWithDefault(
|
|
151
|
+
modal_bottom_sheet_item_label_font_size,
|
|
152
|
+
label1Default?.fontSize
|
|
153
|
+
),
|
|
154
|
+
color: label1Default?.fontColor || modal_bottom_sheet_item_label_font_color,
|
|
96
155
|
focusColor: modal_bottom_sheet_item_label_focus_font_color,
|
|
97
156
|
hoverSelectedColor: modal_bottom_sheet_item_label_hover_selected_font_color,
|
|
98
157
|
selectedColor: modal_bottom_sheet_item_label_selected_font_color,
|
|
@@ -146,7 +205,8 @@ export function getItemLabelProps(
|
|
|
146
205
|
width: number,
|
|
147
206
|
labelProps?:
|
|
148
207
|
| ((theme: PluginConfiguration, width: number) => ItemLabelProps)
|
|
149
|
-
| ItemLabelProps
|
|
208
|
+
| ItemLabelProps,
|
|
209
|
+
blockItemStyle?: Record<string, any>
|
|
150
210
|
) {
|
|
151
211
|
if (labelProps) {
|
|
152
212
|
return typeof labelProps === "function"
|
|
@@ -154,7 +214,7 @@ export function getItemLabelProps(
|
|
|
154
214
|
: labelProps;
|
|
155
215
|
}
|
|
156
216
|
|
|
157
|
-
return defaultItemLabelProps(theme, width);
|
|
217
|
+
return defaultItemLabelProps(theme, width, blockItemStyle);
|
|
158
218
|
}
|
|
159
219
|
|
|
160
220
|
export function getItemProps(
|
|
@@ -162,7 +222,8 @@ export function getItemProps(
|
|
|
162
222
|
width: number,
|
|
163
223
|
itemProps?:
|
|
164
224
|
| ((theme: PluginConfiguration, width: number) => ItemProps)
|
|
165
|
-
| ItemProps
|
|
225
|
+
| ItemProps,
|
|
226
|
+
blockItemStyle?: Record<string, any>
|
|
166
227
|
) {
|
|
167
228
|
if (itemProps) {
|
|
168
229
|
return typeof itemProps === "function"
|
|
@@ -170,5 +231,5 @@ export function getItemProps(
|
|
|
170
231
|
: itemProps;
|
|
171
232
|
}
|
|
172
233
|
|
|
173
|
-
return defaultItemProps(theme, width);
|
|
234
|
+
return defaultItemProps(theme, width, blockItemStyle);
|
|
174
235
|
}
|
|
@@ -1,88 +1,7 @@
|
|
|
1
1
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
2
|
|
|
3
|
-
exports[`OfflineHandler renders 1`] = `
|
|
4
|
-
<
|
|
5
|
-
|
|
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,26 +1,26 @@
|
|
|
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
16
|
...jest.requireActual(
|
|
12
17
|
"@applicaster/zapp-react-native-utils/reactHooks/connection"
|
|
13
18
|
),
|
|
14
|
-
useConnectionInfo: jest.fn(() =>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return {
|
|
20
|
-
...jest.requireActual(
|
|
21
|
-
"@applicaster/zapp-react-native-utils/reactHooks/utils"
|
|
22
|
-
),
|
|
23
|
-
usePrevious: jest.fn(() => mockPreviousValue),
|
|
19
|
+
useConnectionInfo: jest.fn(() => ({
|
|
20
|
+
details: {},
|
|
21
|
+
isConnected: mockConnectionStatus,
|
|
22
|
+
isInternetReachable: mockConnectionStatus,
|
|
23
|
+
})),
|
|
24
24
|
};
|
|
25
25
|
});
|
|
26
26
|
|
|
@@ -54,21 +54,81 @@ jest.mock("react-native-safe-area-context", () => ({
|
|
|
54
54
|
}));
|
|
55
55
|
|
|
56
56
|
describe("OfflineHandler", () => {
|
|
57
|
-
const { OfflineHandler
|
|
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
|
+
);
|
|
58
69
|
|
|
59
|
-
it("renders", () => {
|
|
60
|
-
const { toJSON } = render(<OfflineHandler />);
|
|
61
70
|
expect(toJSON()).toMatchSnapshot();
|
|
62
71
|
});
|
|
63
72
|
|
|
64
|
-
it("
|
|
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", () => {
|
|
65
95
|
mockConnectionStatus = true;
|
|
66
96
|
|
|
67
|
-
const
|
|
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 />);
|
|
68
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;
|
|
69
120
|
rerender(<OfflineHandler />);
|
|
70
121
|
|
|
71
|
-
|
|
72
|
-
|
|
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();
|
|
73
133
|
});
|
|
74
134
|
});
|
|
@@ -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,83 @@
|
|
|
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
|
-
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
|
|
48
|
-
setHidden(true);
|
|
34
|
+
const { message, extraMessage, style } = useOfflineHandlerProps(online);
|
|
49
35
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
timer.current = null;
|
|
53
|
-
}
|
|
54
|
-
}, []);
|
|
36
|
+
const showOnlineToast = useCallback(() => {
|
|
37
|
+
logger.log("Device is back online, emit toast");
|
|
55
38
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
}, [
|
|
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
|
-
|
|
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
|
-
};
|