@applicaster/zapp-react-native-ui-components 16.0.0-alpha.2851031376 → 16.0.0-alpha.3029086545
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/CellRendererResolver/index.ts +1 -1
- package/Components/Layout/FullWidthRow.tsx +38 -0
- 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/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/AudioPlayer/Components/Action.tsx +332 -0
- package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +402 -0
- package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +811 -0
- package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +671 -0
- package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +878 -0
- package/Components/ModalComponent/AudioPlayer/Components/index.ts +9 -0
- package/Components/ModalComponent/BottomSheetDragArea.tsx +33 -0
- package/Components/ModalComponent/BottomSheetModalContent.tsx +49 -26
- package/Components/ModalComponent/__tests__/BottomSheetDragArea.test.tsx +74 -0
- 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 +123 -0
- package/Components/OfflineHandler/index.tsx +51 -73
- package/Components/OfflineHandler/utils/index.ts +1 -13
- package/Components/PlayerContainer/PlayerContainer.tsx +7 -3
- 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/TopCutoffOverlay/index.tsx +2 -2
- package/Components/Transitioner/Transitioner.tsx +28 -17
- 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/package.json +5 -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
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { PanGestureHandler } from "react-native-gesture-handler";
|
|
3
|
+
import { View } from "react-native";
|
|
4
|
+
import { useBottomSheet } from "@applicaster/zapp-react-native-utils/bottomSheet";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Turns its children into a drag zone for the enclosing DraggableBottomSheet:
|
|
8
|
+
* dragging here moves/dismisses the sheet (used for the modal header bar).
|
|
9
|
+
* Outside a sheet (no context) it renders children unchanged.
|
|
10
|
+
*/
|
|
11
|
+
export const BottomSheetDragArea = ({
|
|
12
|
+
children,
|
|
13
|
+
}: {
|
|
14
|
+
children: React.ReactElement;
|
|
15
|
+
}) => {
|
|
16
|
+
const sheet = useBottomSheet();
|
|
17
|
+
|
|
18
|
+
// Outside a sheet (no context) render children unchanged.
|
|
19
|
+
if (!sheet) return children;
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<PanGestureHandler
|
|
23
|
+
simultaneousHandlers={[sheet.masterDrawerRef]}
|
|
24
|
+
shouldCancelWhenOutside={false}
|
|
25
|
+
onGestureEvent={sheet.onDragGestureEvent}
|
|
26
|
+
onHandlerStateChange={sheet.onDragStateChange}
|
|
27
|
+
>
|
|
28
|
+
{/* Old-API PanGestureHandler needs a host-component child; children may
|
|
29
|
+
be a function component (e.g. ModalHeader), so wrap in a View. */}
|
|
30
|
+
<View>{children}</View>
|
|
31
|
+
</PanGestureHandler>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
@@ -6,11 +6,11 @@ import { ItemProps } from "./Button/Item";
|
|
|
6
6
|
import { ItemLabelProps } from "./Button/ItemLabel";
|
|
7
7
|
import { getItemIconProps, getItemLabelProps, getItemProps } from "./utils";
|
|
8
8
|
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
9
|
-
|
|
10
9
|
import type { PluginConfiguration } from "./";
|
|
11
10
|
|
|
12
11
|
import { ModalHeader as DefaultModalHeader } from "./Header";
|
|
13
12
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
13
|
+
import { BottomSheetDragArea } from "./BottomSheetDragArea";
|
|
14
14
|
|
|
15
15
|
type ModalComponentProps = {
|
|
16
16
|
items: any[];
|
|
@@ -23,6 +23,12 @@ type ModalComponentProps = {
|
|
|
23
23
|
maxHeight?: number;
|
|
24
24
|
dismiss: () => void;
|
|
25
25
|
headerComponent?: React.ComponentType;
|
|
26
|
+
|
|
27
|
+
scrollViewContent?: {
|
|
28
|
+
renderItem: (item: any, index: number) => React.ReactNode;
|
|
29
|
+
items: any[];
|
|
30
|
+
} | null;
|
|
31
|
+
|
|
26
32
|
buttonComponent?: React.ComponentType;
|
|
27
33
|
iconProps?: ItemIconProps | ((theme: PluginConfiguration) => ItemIconProps);
|
|
28
34
|
itemProps?:
|
|
@@ -49,7 +55,9 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
49
55
|
dismiss,
|
|
50
56
|
summary,
|
|
51
57
|
title,
|
|
52
|
-
headerComponent
|
|
58
|
+
headerComponent = DefaultModalHeader,
|
|
59
|
+
scrollViewContent = null,
|
|
60
|
+
|
|
53
61
|
buttonComponent: ButtonComponent = Button,
|
|
54
62
|
getSelectedItemIcon = ({
|
|
55
63
|
modal_bottom_sheet_item_selected_icon,
|
|
@@ -63,6 +71,8 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
63
71
|
const paddingTop = Number(theme.modal_bottom_sheet_padding_top);
|
|
64
72
|
const paddingBottom = Number(theme.modal_bottom_sheet_padding_bottom);
|
|
65
73
|
|
|
74
|
+
const Header = headerComponent;
|
|
75
|
+
|
|
66
76
|
useEffect(() => {
|
|
67
77
|
if (currentRoute !== route.current) {
|
|
68
78
|
props.dismiss();
|
|
@@ -98,35 +108,48 @@ export function BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
98
108
|
paddingTop: paddingTop,
|
|
99
109
|
}}
|
|
100
110
|
>
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
{Header ? (
|
|
112
|
+
<BottomSheetDragArea>
|
|
113
|
+
<Header
|
|
114
|
+
dismiss={dismiss}
|
|
115
|
+
configuration={theme}
|
|
116
|
+
summary={summary}
|
|
117
|
+
title={title}
|
|
118
|
+
/>
|
|
119
|
+
</BottomSheetDragArea>
|
|
120
|
+
) : null}
|
|
107
121
|
<ScrollView
|
|
122
|
+
overScrollMode="never"
|
|
123
|
+
bounces={false}
|
|
124
|
+
showsVerticalScrollIndicator={false}
|
|
108
125
|
contentContainerStyle={{
|
|
109
126
|
paddingBottom: paddingBottom + bottomInset,
|
|
110
127
|
}}
|
|
111
128
|
>
|
|
112
|
-
{
|
|
113
|
-
item
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
{scrollViewContent
|
|
130
|
+
? scrollViewContent.items.map((item, index) =>
|
|
131
|
+
scrollViewContent.renderItem(item, index)
|
|
132
|
+
)
|
|
133
|
+
: null}
|
|
134
|
+
{!scrollViewContent &&
|
|
135
|
+
items.map((item, index) =>
|
|
136
|
+
item ? (
|
|
137
|
+
<ButtonComponent
|
|
138
|
+
key={index}
|
|
139
|
+
configuration={theme}
|
|
140
|
+
selectedItem={current_selection}
|
|
141
|
+
item={item}
|
|
142
|
+
onPress={handlePress}
|
|
143
|
+
label={theme[item?.label] ?? item?.label}
|
|
144
|
+
iconBaseProps={iconBaseProps}
|
|
145
|
+
itemBaseProps={itemBaseProps}
|
|
146
|
+
labelBaseProps={labelBaseProps}
|
|
147
|
+
selectedItemIcon={getSelectedItemIcon(theme)}
|
|
148
|
+
defaultItemIcon={getDefaultItemIcon(theme)}
|
|
149
|
+
iconPlacement={iconPlacement}
|
|
150
|
+
/>
|
|
151
|
+
) : null
|
|
152
|
+
)}
|
|
130
153
|
</ScrollView>
|
|
131
154
|
</View>
|
|
132
155
|
);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Text } from "react-native";
|
|
3
|
+
import { render, screen } from "@testing-library/react-native";
|
|
4
|
+
import { BottomSheetDragArea } from "../BottomSheetDragArea";
|
|
5
|
+
import { BottomSheetContext } from "@applicaster/zapp-react-native-utils/bottomSheet";
|
|
6
|
+
|
|
7
|
+
// Reveal PanGestureHandler and capture its props.
|
|
8
|
+
jest.mock("react-native-gesture-handler", () => {
|
|
9
|
+
const { View } = require("react-native");
|
|
10
|
+
const actual = jest.requireActual("react-native-gesture-handler");
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
__esModule: true,
|
|
14
|
+
...actual,
|
|
15
|
+
PanGestureHandler: ({
|
|
16
|
+
children,
|
|
17
|
+
onGestureEvent,
|
|
18
|
+
onHandlerStateChange,
|
|
19
|
+
simultaneousHandlers,
|
|
20
|
+
}: any) => (
|
|
21
|
+
<View
|
|
22
|
+
testID="drag-pan"
|
|
23
|
+
onGestureEvent={onGestureEvent}
|
|
24
|
+
onHandlerStateChange={onHandlerStateChange}
|
|
25
|
+
simultaneousHandlers={simultaneousHandlers}
|
|
26
|
+
>
|
|
27
|
+
{children}
|
|
28
|
+
</View>
|
|
29
|
+
),
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const makeValue = () => ({
|
|
34
|
+
scrollRef: React.createRef<any>(),
|
|
35
|
+
masterDrawerRef: React.createRef<any>(),
|
|
36
|
+
drawerRef: React.createRef<any>(),
|
|
37
|
+
onScroll: jest.fn(),
|
|
38
|
+
setBodyDragEnabled: jest.fn(),
|
|
39
|
+
onDragGestureEvent: jest.fn(),
|
|
40
|
+
onDragStateChange: jest.fn(),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("BottomSheetDragArea", () => {
|
|
44
|
+
it("wraps children in a sheet-wired PanGestureHandler when inside a sheet", () => {
|
|
45
|
+
const value = makeValue();
|
|
46
|
+
|
|
47
|
+
render(
|
|
48
|
+
<BottomSheetContext.Provider value={value}>
|
|
49
|
+
<BottomSheetDragArea>
|
|
50
|
+
<Text testID="child">hi</Text>
|
|
51
|
+
</BottomSheetDragArea>
|
|
52
|
+
</BottomSheetContext.Provider>
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const pan = screen.getByTestId("drag-pan");
|
|
56
|
+
expect(pan.props.onGestureEvent).toBe(value.onDragGestureEvent);
|
|
57
|
+
expect(pan.props.onHandlerStateChange).toBe(value.onDragStateChange);
|
|
58
|
+
|
|
59
|
+
expect(pan.props.simultaneousHandlers).toEqual([value.masterDrawerRef]);
|
|
60
|
+
|
|
61
|
+
expect(screen.getByTestId("child")).toBeTruthy();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("renders children bare when used outside a sheet", () => {
|
|
65
|
+
render(
|
|
66
|
+
<BottomSheetDragArea>
|
|
67
|
+
<Text testID="child">hi</Text>
|
|
68
|
+
</BottomSheetDragArea>
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
expect(screen.queryByTestId("drag-pan")).toBeNull();
|
|
72
|
+
expect(screen.getByTestId("child")).toBeTruthy();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -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,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
|
+
}
|