@applicaster/zapp-react-native-ui-components 15.0.0-alpha.1690332913 → 15.0.0-alpha.1693300296
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/Cell/Cell.tsx +6 -0
- package/Components/GeneralContentScreen/GeneralContentScreen.tsx +39 -28
- package/Components/GeneralContentScreen/__tests__/GeneralContentScreen.test.tsx +104 -0
- package/Components/GeneralContentScreen/utils/__tests__/getScreenDataSource.test.ts +19 -0
- package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
- package/Components/GeneralContentScreen/utils/getScreenDataSource.ts +9 -0
- package/Components/GeneralContentScreen/utils/useCurationAPI.ts +7 -7
- package/Components/HandlePlayable/HandlePlayable.tsx +16 -29
- package/Components/HandlePlayable/utils.ts +31 -0
- package/Components/HookRenderer/HookRenderer.tsx +40 -10
- package/Components/HookRenderer/__tests__/HookRenderer.test.tsx +60 -0
- package/Components/Layout/TV/NavBarContainer.tsx +1 -10
- package/Components/Layout/TV/__tests__/__snapshots__/NavBarContainer.test.tsx.snap +7 -12
- package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +7 -12
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/model.test.ts +80 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +187 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/selectors.test.ts +45 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/style.test.ts +49 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/model.ts +47 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +170 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/selectors.ts +26 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/style.ts +29 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/types.ts +37 -0
- package/Components/MasterCell/DefaultComponents/Button.tsx +0 -15
- package/Components/MasterCell/DefaultComponents/LiveImage/__tests__/prepareEntry.test.ts +352 -0
- package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +136 -0
- package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +33 -16
- package/Components/MasterCell/DefaultComponents/PressableView.tsx +196 -0
- package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +40 -39
- package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/Image.test.tsx +95 -0
- package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +86 -0
- package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/index.test.ts +141 -0
- package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +7 -6
- package/Components/MasterCell/DefaultComponents/SecondaryImage/index.ts +1 -1
- package/Components/MasterCell/DefaultComponents/Text/index.tsx +2 -6
- package/Components/MasterCell/DefaultComponents/index.ts +2 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +46 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +126 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ButtonContainerView.ts +23 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +16 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabel.ts +67 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +32 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +191 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +140 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +222 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/helpers.ts +105 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +104 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +118 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +73 -0
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +86 -0
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +35 -48
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +115 -29
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +39 -144
- package/Components/MasterCell/elementMapper.tsx +1 -0
- package/Components/MasterCell/hoc/__tests__/withAsyncRender.test.tsx +219 -0
- package/Components/MasterCell/hoc/withAsyncRender.tsx +9 -7
- package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +17 -9
- package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +16 -8
- package/Components/OfflineHandler/NotificationView/utils.ts +34 -0
- package/Components/PlayerContainer/PlayerContainer.tsx +1 -18
- package/Components/PreloaderWrapper/__tests__/index.test.tsx +26 -0
- package/Components/PreloaderWrapper/index.tsx +15 -0
- package/Components/River/ComponentsMap/ComponentsMap.tsx +16 -0
- package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
- package/Components/River/RefreshControl.tsx +36 -13
- package/Components/River/RiverItem.tsx +26 -20
- package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +2 -0
- package/Components/River/__tests__/componentsMap.test.js +38 -0
- package/Components/Screen/__tests__/Screen.test.tsx +1 -0
- package/Components/Screen/hooks.ts +73 -3
- package/Components/Screen/index.tsx +7 -1
- package/Components/Screen/orientationHandler.ts +7 -10
- package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +46 -0
- package/Components/ScreenFeedLoader/__tests__/ScreenFeedLoader.test.tsx +94 -0
- package/Components/ScreenFeedLoader/index.ts +1 -0
- package/Components/ScreenResolver/__tests__/screenResolver.test.js +24 -0
- package/Components/ScreenResolver/hooks/index.ts +3 -0
- package/Components/ScreenResolver/hooks/useGetComponent.ts +15 -0
- package/Components/ScreenResolver/hooks/useScreenComponentResolver.tsx +90 -0
- package/Components/ScreenResolver/index.tsx +15 -117
- package/Components/ScreenResolver/utils/__tests__/getScreenTypeProps.test.ts +45 -0
- package/Components/ScreenResolver/utils/getScreenTypeProps.ts +43 -0
- package/Components/ScreenResolver/utils/index.ts +1 -0
- package/Components/ScreenResolver/withDefaultScreenContext.tsx +16 -0
- package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +25 -0
- package/Components/ScreenResolverFeedProvider/__tests__/ScreenResolverFeedProvider.test.tsx +44 -0
- package/Components/ScreenResolverFeedProvider/index.ts +1 -0
- package/Components/Tabs/TabContent.tsx +7 -4
- package/Components/Transitioner/Scene.tsx +9 -15
- package/Components/VideoLive/LiveImageManager.ts +199 -54
- package/Components/VideoLive/PlayerLiveImageComponent.tsx +31 -33
- package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +2 -17
- package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +15 -7
- package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
- package/Components/Viewport/ViewportAware/index.tsx +16 -7
- package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
- package/Components/ZappUIComponent/index.tsx +12 -6
- package/Components/default-cell-renderer/viewTrees/mobile/index.ts +0 -3
- package/Components/index.js +1 -1
- package/Contexts/ScreenContext/__tests__/index.test.tsx +57 -0
- package/Contexts/ScreenContext/index.tsx +71 -19
- package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
- package/Contexts/ZappHookModalContext/index.tsx +37 -61
- package/Contexts/ZappPipesContext/ZappPipesContextFactory.tsx +18 -7
- package/Contexts/index.ts +0 -2
- package/Decorators/ZappPipesDataConnector/ResolverSelector.tsx +25 -7
- package/Decorators/ZappPipesDataConnector/__tests__/ResolverSelector.test.tsx +212 -5
- package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
- package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
- package/events/index.ts +3 -0
- package/events/scrollEndReached.ts +15 -0
- package/package.json +5 -5
- package/Components/MasterCell/DefaultComponents/Text/utils/__tests__/withAdjustedLineHeight.test.ts +0 -46
- package/Components/MasterCell/DefaultComponents/Text/utils/index.ts +0 -21
- package/Components/PlayerContainer/ErrorDisplay/ErrorDisplay.tsx +0 -57
- package/Components/PlayerContainer/ErrorDisplay/index.ts +0 -9
- package/Components/PlayerContainer/useRestrictMobilePlayback.tsx +0 -101
- /package/Components/HookRenderer/{index.tsx → index.ts} +0 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render } from "@testing-library/react-native";
|
|
3
|
+
import { Text, View } from "react-native";
|
|
4
|
+
import { withAsyncRenderHOC } from "../withAsyncRender";
|
|
5
|
+
|
|
6
|
+
describe("withAsyncRenderHOC", () => {
|
|
7
|
+
// Test component that requires onAsyncRender
|
|
8
|
+
const TestComponent = ({
|
|
9
|
+
onAsyncRender,
|
|
10
|
+
text,
|
|
11
|
+
}: {
|
|
12
|
+
onAsyncRender: () => void;
|
|
13
|
+
text?: string;
|
|
14
|
+
}) => {
|
|
15
|
+
React.useEffect(() => {
|
|
16
|
+
onAsyncRender();
|
|
17
|
+
}, [onAsyncRender]);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<View>
|
|
21
|
+
<Text>{text || "Test"}</Text>
|
|
22
|
+
</View>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
it("should wrap component and provide onAsyncRender callback", () => {
|
|
27
|
+
const WrappedComponent = withAsyncRenderHOC(TestComponent);
|
|
28
|
+
|
|
29
|
+
const wrapper = render(<WrappedComponent text="Hello" />);
|
|
30
|
+
|
|
31
|
+
expect(wrapper.toJSON()).not.toBeNull();
|
|
32
|
+
expect(wrapper.getByText("Hello")).toBeDefined();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should call emitAsyncElementRegistrate on mount", () => {
|
|
36
|
+
const mockRegistrate = jest.fn();
|
|
37
|
+
const WrappedComponent = withAsyncRenderHOC(TestComponent);
|
|
38
|
+
|
|
39
|
+
render(<WrappedComponent emitAsyncElementRegistrate={mockRegistrate} />);
|
|
40
|
+
|
|
41
|
+
expect(mockRegistrate).toHaveBeenCalledTimes(1);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("should call emitAsyncElementLayout when onAsyncRender is called", () => {
|
|
45
|
+
const mockLayout = jest.fn();
|
|
46
|
+
|
|
47
|
+
const ComponentWithAsyncCall = ({
|
|
48
|
+
onAsyncRender,
|
|
49
|
+
}: {
|
|
50
|
+
onAsyncRender: () => void;
|
|
51
|
+
}) => {
|
|
52
|
+
return (
|
|
53
|
+
<View onLayout={onAsyncRender}>
|
|
54
|
+
<Text>Async Component</Text>
|
|
55
|
+
</View>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const WrappedComponent = withAsyncRenderHOC(ComponentWithAsyncCall);
|
|
60
|
+
|
|
61
|
+
const wrapper = render(
|
|
62
|
+
<WrappedComponent emitAsyncElementLayout={mockLayout} />
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Trigger onLayout
|
|
66
|
+
const view = wrapper.UNSAFE_getByType(View);
|
|
67
|
+
view.props.onLayout();
|
|
68
|
+
|
|
69
|
+
expect(mockLayout).toHaveBeenCalled();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should return unsubscribe function from emitAsyncElementRegistrate", () => {
|
|
73
|
+
const mockUnsubscribe = jest.fn();
|
|
74
|
+
const mockRegistrate = jest.fn(() => mockUnsubscribe);
|
|
75
|
+
const WrappedComponent = withAsyncRenderHOC(TestComponent);
|
|
76
|
+
|
|
77
|
+
const { unmount } = render(
|
|
78
|
+
<WrappedComponent emitAsyncElementRegistrate={mockRegistrate} />
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
expect(mockRegistrate).toHaveBeenCalledTimes(1);
|
|
82
|
+
|
|
83
|
+
// Unmount should trigger cleanup
|
|
84
|
+
unmount();
|
|
85
|
+
|
|
86
|
+
expect(mockUnsubscribe).toHaveBeenCalled();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should work without emitAsyncElementRegistrate prop", () => {
|
|
90
|
+
const WrappedComponent = withAsyncRenderHOC(TestComponent);
|
|
91
|
+
|
|
92
|
+
const wrapper = render(<WrappedComponent text="No Registration" />);
|
|
93
|
+
|
|
94
|
+
expect(wrapper.toJSON()).not.toBeNull();
|
|
95
|
+
expect(wrapper.getByText("No Registration")).toBeDefined();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should work without emitAsyncElementLayout prop", () => {
|
|
99
|
+
const ComponentWithLayout = ({
|
|
100
|
+
onAsyncRender,
|
|
101
|
+
}: {
|
|
102
|
+
onAsyncRender: () => void;
|
|
103
|
+
}) => {
|
|
104
|
+
return (
|
|
105
|
+
<View onLayout={onAsyncRender}>
|
|
106
|
+
<Text>Test Layout</Text>
|
|
107
|
+
</View>
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const WrappedComponent = withAsyncRenderHOC(ComponentWithLayout);
|
|
112
|
+
|
|
113
|
+
const wrapper = render(<WrappedComponent />);
|
|
114
|
+
|
|
115
|
+
// Should not throw when calling onLayout without emitAsyncElementLayout
|
|
116
|
+
const view = wrapper.UNSAFE_getByType(View);
|
|
117
|
+
expect(() => view.props.onLayout()).not.toThrow();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should pass through all other props to wrapped component", () => {
|
|
121
|
+
const ComponentWithProps = ({
|
|
122
|
+
customProp,
|
|
123
|
+
anotherProp,
|
|
124
|
+
}: {
|
|
125
|
+
onAsyncRender: () => void;
|
|
126
|
+
customProp: string;
|
|
127
|
+
anotherProp: number;
|
|
128
|
+
}) => {
|
|
129
|
+
return (
|
|
130
|
+
<View>
|
|
131
|
+
<Text>{customProp}</Text>
|
|
132
|
+
<Text>{anotherProp}</Text>
|
|
133
|
+
</View>
|
|
134
|
+
);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const WrappedComponent = withAsyncRenderHOC(ComponentWithProps);
|
|
138
|
+
|
|
139
|
+
const wrapper = render(
|
|
140
|
+
<WrappedComponent customProp="custom value" anotherProp={42} />
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
expect(wrapper.getByText("custom value")).toBeDefined();
|
|
144
|
+
expect(wrapper.getByText("42")).toBeDefined();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should call emitAsyncElementLayout multiple times if onAsyncRender is called multiple times", () => {
|
|
148
|
+
const mockLayout = jest.fn();
|
|
149
|
+
|
|
150
|
+
const ComponentWithMultipleCalls = ({
|
|
151
|
+
onAsyncRender,
|
|
152
|
+
}: {
|
|
153
|
+
onAsyncRender: () => void;
|
|
154
|
+
}) => {
|
|
155
|
+
return (
|
|
156
|
+
<View>
|
|
157
|
+
<View onLayout={onAsyncRender} testID="view1">
|
|
158
|
+
<Text>First</Text>
|
|
159
|
+
</View>
|
|
160
|
+
<View onLayout={onAsyncRender} testID="view2">
|
|
161
|
+
<Text>Second</Text>
|
|
162
|
+
</View>
|
|
163
|
+
</View>
|
|
164
|
+
);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const WrappedComponent = withAsyncRenderHOC(ComponentWithMultipleCalls);
|
|
168
|
+
|
|
169
|
+
const wrapper = render(
|
|
170
|
+
<WrappedComponent emitAsyncElementLayout={mockLayout} />
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// Trigger both onLayout callbacks
|
|
174
|
+
const view1 = wrapper.getByTestId("view1");
|
|
175
|
+
const view2 = wrapper.getByTestId("view2");
|
|
176
|
+
|
|
177
|
+
view1.props.onLayout();
|
|
178
|
+
view2.props.onLayout();
|
|
179
|
+
|
|
180
|
+
expect(mockLayout).toHaveBeenCalledTimes(2);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("should handle components that use onAsyncRender in effects", () => {
|
|
184
|
+
const mockLayout = jest.fn();
|
|
185
|
+
|
|
186
|
+
const ComponentWithEffect = ({
|
|
187
|
+
onAsyncRender,
|
|
188
|
+
}: {
|
|
189
|
+
onAsyncRender: () => void;
|
|
190
|
+
}) => {
|
|
191
|
+
React.useEffect(() => {
|
|
192
|
+
// Simulate async operation completing
|
|
193
|
+
const timer = setTimeout(() => {
|
|
194
|
+
onAsyncRender();
|
|
195
|
+
}, 0);
|
|
196
|
+
|
|
197
|
+
return () => clearTimeout(timer);
|
|
198
|
+
}, [onAsyncRender]);
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<View>
|
|
202
|
+
<Text>Effect Component</Text>
|
|
203
|
+
</View>
|
|
204
|
+
);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const WrappedComponent = withAsyncRenderHOC(ComponentWithEffect);
|
|
208
|
+
|
|
209
|
+
render(<WrappedComponent emitAsyncElementLayout={mockLayout} />);
|
|
210
|
+
|
|
211
|
+
// The effect should have been triggered
|
|
212
|
+
return new Promise((resolve) => {
|
|
213
|
+
setTimeout(() => {
|
|
214
|
+
expect(mockLayout).toHaveBeenCalled();
|
|
215
|
+
resolve(true);
|
|
216
|
+
}, 10);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
});
|
|
@@ -6,12 +6,12 @@ type withAsyncRenderHOCProps = {
|
|
|
6
6
|
emitAsyncElementLayout?: () => void;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
Component: React.ComponentType<
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
export const withAsyncRenderHOC = <P extends { onAsyncRender: () => void }>(
|
|
10
|
+
Component: React.ComponentType<P>
|
|
11
|
+
): React.FC<Omit<P, "onAsyncRender"> & withAsyncRenderHOCProps> => {
|
|
12
|
+
const WithAsyncRender = (
|
|
13
|
+
props: Omit<P, "onAsyncRender"> & withAsyncRenderHOCProps
|
|
14
|
+
) => {
|
|
15
15
|
const { emitAsyncElementRegistrate = noop, emitAsyncElementLayout = noop } =
|
|
16
16
|
props;
|
|
17
17
|
|
|
@@ -27,7 +27,9 @@ export const withAsyncRenderHOC: withAsyncRenderHOCT = (Component) => {
|
|
|
27
27
|
}
|
|
28
28
|
}, [emitAsyncElementLayout]);
|
|
29
29
|
|
|
30
|
-
return
|
|
30
|
+
return (
|
|
31
|
+
<Component {...(props as unknown as P)} onAsyncRender={onAsyncRender} />
|
|
32
|
+
);
|
|
31
33
|
};
|
|
32
34
|
|
|
33
35
|
return WithAsyncRender;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
3
|
+
import { useNetworkStatusLocalizations } from "./utils";
|
|
2
4
|
|
|
3
5
|
type Props = {
|
|
4
6
|
children?: React.ReactNode;
|
|
@@ -10,13 +12,6 @@ type Props = {
|
|
|
10
12
|
|
|
11
13
|
const NOTIF_DURATION = 4500;
|
|
12
14
|
|
|
13
|
-
export const ONLINE_MSG = "You are back online";
|
|
14
|
-
|
|
15
|
-
export const OFFLINE_MSG = "No internet connection";
|
|
16
|
-
|
|
17
|
-
const EXTRA_OFFLINE_MSG = "Please check your connection";
|
|
18
|
-
const EXTRA_ONLINE_MSG = "Feel free to continue where you left off";
|
|
19
|
-
|
|
20
15
|
const styles: Record<any, React.CSSProperties> = {
|
|
21
16
|
body: {
|
|
22
17
|
position: "absolute",
|
|
@@ -47,6 +42,9 @@ const styles: Record<any, React.CSSProperties> = {
|
|
|
47
42
|
export const NotificationView = (props: Props) => {
|
|
48
43
|
const { children, hidden, dismiss, previousOnline, online } = props;
|
|
49
44
|
|
|
45
|
+
const theme = useTheme<BaseThemePropertiesTV>();
|
|
46
|
+
const storedLocalizations = useNetworkStatusLocalizations();
|
|
47
|
+
|
|
50
48
|
const [open, setOpen] = React.useState<boolean | null>(null);
|
|
51
49
|
|
|
52
50
|
const timeout = React.useRef<NodeJS.Timeout>();
|
|
@@ -84,8 +82,18 @@ export const NotificationView = (props: Props) => {
|
|
|
84
82
|
}, [hidden, online]);
|
|
85
83
|
|
|
86
84
|
const showOnlineMsg = wentOnline || online;
|
|
87
|
-
|
|
88
|
-
const
|
|
85
|
+
|
|
86
|
+
const MSG = showOnlineMsg
|
|
87
|
+
? (theme?.online_notification_title ??
|
|
88
|
+
storedLocalizations?.online_notification_title)
|
|
89
|
+
: (theme?.offline_notification_title ??
|
|
90
|
+
storedLocalizations?.offline_notification_title);
|
|
91
|
+
|
|
92
|
+
const EXTRA_MSG = showOnlineMsg
|
|
93
|
+
? (theme?.online_notification_subtitle ??
|
|
94
|
+
storedLocalizations?.online_notification_subtitle)
|
|
95
|
+
: (theme?.offline_notification_subtitle ??
|
|
96
|
+
storedLocalizations?.offline_notification_subtitle);
|
|
89
97
|
|
|
90
98
|
return (
|
|
91
99
|
<div onClick={onClose}>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
3
|
+
import { useNetworkStatusLocalizations } from "./utils";
|
|
2
4
|
|
|
3
5
|
type Props = {
|
|
4
6
|
children?: React.ReactNode;
|
|
@@ -9,12 +11,6 @@ type Props = {
|
|
|
9
11
|
|
|
10
12
|
const DURATION_TO_HIDE_AFTER_BACK_TO_ONLINE = 4500; // ms
|
|
11
13
|
|
|
12
|
-
const ONLINE_TITLE = "You are back online";
|
|
13
|
-
const ONLINE_SUBTITLE = "Feel free to continue where you left off";
|
|
14
|
-
|
|
15
|
-
const OFFLINE_TITLE = "No internet connection";
|
|
16
|
-
const OFFLINE_SUBTITLE = "Please check your connection";
|
|
17
|
-
|
|
18
14
|
const styles: Record<any, React.CSSProperties> = {
|
|
19
15
|
body: {
|
|
20
16
|
position: "absolute",
|
|
@@ -47,6 +43,9 @@ let timer: NodeJS.Timeout;
|
|
|
47
43
|
export const NotificationView = (props: Props) => {
|
|
48
44
|
const { children, dismiss, previousOnline = true, online } = props;
|
|
49
45
|
|
|
46
|
+
const theme = useTheme<BaseThemePropertiesTV>();
|
|
47
|
+
const storedLocalizations = useNetworkStatusLocalizations();
|
|
48
|
+
|
|
50
49
|
const [shown, setShown] = React.useState<boolean>(false);
|
|
51
50
|
|
|
52
51
|
const onClose = () => {
|
|
@@ -79,8 +78,17 @@ export const NotificationView = (props: Props) => {
|
|
|
79
78
|
}
|
|
80
79
|
}, [online, previousOnline]);
|
|
81
80
|
|
|
82
|
-
const title = online
|
|
83
|
-
|
|
81
|
+
const title = online
|
|
82
|
+
? (theme?.online_notification_title ??
|
|
83
|
+
storedLocalizations?.online_notification_title)
|
|
84
|
+
: (theme?.offline_notification_title ??
|
|
85
|
+
storedLocalizations?.offline_notification_title);
|
|
86
|
+
|
|
87
|
+
const subtitle = online
|
|
88
|
+
? (theme?.online_notification_subtitle ??
|
|
89
|
+
storedLocalizations?.online_notification_subtitle)
|
|
90
|
+
: (theme?.offline_notification_subtitle ??
|
|
91
|
+
storedLocalizations?.offline_notification_subtitle);
|
|
84
92
|
|
|
85
93
|
return (
|
|
86
94
|
<div onClick={onClose}>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { sessionStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/SessionStorage";
|
|
3
|
+
|
|
4
|
+
export const NETWORK_STATUS_LOCALIZATIONS_KEY = "network_status_localizations";
|
|
5
|
+
|
|
6
|
+
export const THEME_STORAGE_NAMESPACE = "quick-brick-theme";
|
|
7
|
+
|
|
8
|
+
export function useNetworkStatusLocalizations() {
|
|
9
|
+
const [storedLocalizations, setStoredLocalizations] = React.useState<Record<
|
|
10
|
+
string,
|
|
11
|
+
string
|
|
12
|
+
> | null>(null);
|
|
13
|
+
|
|
14
|
+
React.useEffect(() => {
|
|
15
|
+
async function loadStoredLocalizations() {
|
|
16
|
+
try {
|
|
17
|
+
const stored = await sessionStorage.getItem(
|
|
18
|
+
NETWORK_STATUS_LOCALIZATIONS_KEY,
|
|
19
|
+
THEME_STORAGE_NAMESPACE
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
if (stored) {
|
|
23
|
+
setStoredLocalizations(stored);
|
|
24
|
+
}
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error("Error loading network status localizations", error);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
loadStoredLocalizations();
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
return storedLocalizations;
|
|
34
|
+
}
|
|
@@ -46,7 +46,6 @@ import {
|
|
|
46
46
|
PlayerContainerContextProvider,
|
|
47
47
|
} from "./PlayerContainerContext";
|
|
48
48
|
import { FocusableGroup } from "@applicaster/zapp-react-native-ui-components/Components/FocusableGroup";
|
|
49
|
-
import { ErrorDisplay } from "./ErrorDisplay";
|
|
50
49
|
import { PlayerFocusableWrapperView } from "./WappersView/PlayerFocusableWrapperView";
|
|
51
50
|
import { FocusableGroupMainContainerId } from "./index";
|
|
52
51
|
import { isPlayable } from "@applicaster/zapp-react-native-utils/navigationUtils/itemTypeMatchers";
|
|
@@ -61,7 +60,6 @@ import {
|
|
|
61
60
|
PlayerNativeSendCommand,
|
|
62
61
|
} from "@applicaster/zapp-react-native-utils/appUtils/playerManager/playerNativeCommand";
|
|
63
62
|
import { useAppData } from "@applicaster/zapp-react-native-redux";
|
|
64
|
-
import { useRestrictMobilePlayback } from "./useRestrictMobilePlayback";
|
|
65
63
|
|
|
66
64
|
type Props = {
|
|
67
65
|
Player: React.ComponentType<any>;
|
|
@@ -274,13 +272,6 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
274
272
|
);
|
|
275
273
|
}, [playerManager.isRegistered()]);
|
|
276
274
|
|
|
277
|
-
const { isRestricted } = useRestrictMobilePlayback({
|
|
278
|
-
player,
|
|
279
|
-
entry: item,
|
|
280
|
-
pluginConfiguration,
|
|
281
|
-
close,
|
|
282
|
-
});
|
|
283
|
-
|
|
284
275
|
const playEntry = (entry) => navigator.replaceTop(entry, { mode });
|
|
285
276
|
|
|
286
277
|
const onPlayNextPerformNextVideoPlay = React.useCallback(() => {
|
|
@@ -347,12 +338,6 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
347
338
|
playerContainerLogger.error(errorObj);
|
|
348
339
|
|
|
349
340
|
setState({ error: errorObj });
|
|
350
|
-
|
|
351
|
-
if (!isTvOS) {
|
|
352
|
-
setTimeout(() => {
|
|
353
|
-
close();
|
|
354
|
-
}, 800);
|
|
355
|
-
}
|
|
356
341
|
},
|
|
357
342
|
[close]
|
|
358
343
|
);
|
|
@@ -670,7 +655,7 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
670
655
|
<PlayerFocusableWrapperView
|
|
671
656
|
nextFocusDown={context.bottomFocusableId}
|
|
672
657
|
>
|
|
673
|
-
{
|
|
658
|
+
{!Player ? null : (
|
|
674
659
|
<Player
|
|
675
660
|
source={{
|
|
676
661
|
uri,
|
|
@@ -703,8 +688,6 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
703
688
|
</Player>
|
|
704
689
|
)}
|
|
705
690
|
</PlayerFocusableWrapperView>
|
|
706
|
-
|
|
707
|
-
{state.error ? <ErrorDisplay error={state.error} /> : null}
|
|
708
691
|
</View>
|
|
709
692
|
{/* Components container */}
|
|
710
693
|
{isInlineTV && context.showComponentsContainer ? (
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Text } from "react-native";
|
|
3
|
+
import { render } from "@testing-library/react-native";
|
|
4
|
+
import { PreloaderWrapper } from "..";
|
|
5
|
+
|
|
6
|
+
describe("PreloaderWrapper", () => {
|
|
7
|
+
it("renders children when preloader is hidden", () => {
|
|
8
|
+
const { getByText } = render(
|
|
9
|
+
<PreloaderWrapper showPreloader={false}>
|
|
10
|
+
<Text>content</Text>
|
|
11
|
+
</PreloaderWrapper>
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
expect(getByText("content")).toBeDefined();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("renders null when preloader is shown", () => {
|
|
18
|
+
const { queryByText } = render(
|
|
19
|
+
<PreloaderWrapper showPreloader>
|
|
20
|
+
<Text>content</Text>
|
|
21
|
+
</PreloaderWrapper>
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
expect(queryByText("content")).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
type PreloaderWrapperProps = {
|
|
4
|
+
showPreloader?: boolean;
|
|
5
|
+
children?: React.ReactNode;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const PreloaderWrapper: React.FC<PreloaderWrapperProps> = ({
|
|
9
|
+
showPreloader = false,
|
|
10
|
+
children,
|
|
11
|
+
}) => {
|
|
12
|
+
return !showPreloader ? children : null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default PreloaderWrapper;
|
|
@@ -23,6 +23,7 @@ import { isLast } from "@applicaster/zapp-react-native-utils/arrayUtils";
|
|
|
23
23
|
import { withComponentsMapProvider } from "@applicaster/zapp-react-native-ui-components/Decorators/ComponentsMapWrapper";
|
|
24
24
|
import { useScreenContextV2 } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
|
|
25
25
|
import { useShallow } from "zustand/react/shallow";
|
|
26
|
+
import { emitScrollEndReached } from "@applicaster/zapp-react-native-ui-components/events";
|
|
26
27
|
|
|
27
28
|
import { isAndroidPlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
28
29
|
import { ComponentsMapHeightContext } from "./ContextProviders/ComponentsMapHeightContext";
|
|
@@ -73,6 +74,7 @@ function ComponentsMapComponent(props: Props) {
|
|
|
73
74
|
|
|
74
75
|
const flatListRef = React.useRef<FlatList | null>(null);
|
|
75
76
|
const flatListWrapperRef = React.useRef<View | null>(null);
|
|
77
|
+
const hasUserScrolledRef = React.useRef(false);
|
|
76
78
|
const screenConfig = useScreenConfiguration(riverId);
|
|
77
79
|
const screenData = useScreenData(riverId);
|
|
78
80
|
const pullToRefreshEnabled = screenData?.rules?.pull_to_refresh_enabled;
|
|
@@ -236,6 +238,8 @@ function ComponentsMapComponent(props: Props) {
|
|
|
236
238
|
}, []);
|
|
237
239
|
|
|
238
240
|
const onScroll = React.useCallback((event) => {
|
|
241
|
+
hasUserScrolledRef.current = true;
|
|
242
|
+
|
|
239
243
|
const {
|
|
240
244
|
nativeEvent: {
|
|
241
245
|
contentOffset: { y },
|
|
@@ -277,6 +281,7 @@ function ComponentsMapComponent(props: Props) {
|
|
|
277
281
|
>
|
|
278
282
|
<ViewportTracker>
|
|
279
283
|
<FlatList
|
|
284
|
+
testID="components-map-flat-list"
|
|
280
285
|
ref={(ref) => {
|
|
281
286
|
flatListRef.current = ref;
|
|
282
287
|
}}
|
|
@@ -308,6 +313,17 @@ function ComponentsMapComponent(props: Props) {
|
|
|
308
313
|
onScrollEndDrag={_onScrollEndDrag}
|
|
309
314
|
scrollEventThrottle={16}
|
|
310
315
|
{...scrollViewExtraProps}
|
|
316
|
+
onEndReached={
|
|
317
|
+
/* When wrapped in a parent ScrollView (e.g. tabs),
|
|
318
|
+
this FlatList doesn't scroll so onEndReached can fire repeatedly;
|
|
319
|
+
skip it here and let the parent ScrollView emit scroll-end instead. */
|
|
320
|
+
isScreenWrappedInContainer
|
|
321
|
+
? undefined
|
|
322
|
+
: () => {
|
|
323
|
+
if (!hasUserScrolledRef.current) return;
|
|
324
|
+
emitScrollEndReached();
|
|
325
|
+
}
|
|
326
|
+
}
|
|
311
327
|
/>
|
|
312
328
|
</ViewportTracker>
|
|
313
329
|
</ScreenLoadingMeasurements>
|
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
StyleSheet,
|
|
6
6
|
} from "react-native";
|
|
7
7
|
import * as R from "ramda";
|
|
8
|
+
import { path } from "@applicaster/zapp-react-native-utils/utils";
|
|
9
|
+
import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
8
10
|
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
9
11
|
import { useLocalizedStrings } from "@applicaster/zapp-react-native-utils/localizationUtils";
|
|
10
12
|
import { useAnalytics } from "@applicaster/zapp-react-native-utils/analyticsUtils";
|
|
@@ -14,6 +16,7 @@ import { useShallow } from "zustand/react/shallow";
|
|
|
14
16
|
import { useScreenContextV2 } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
|
|
15
17
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
16
18
|
import { useLoadPipesDataDispatch } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
19
|
+
import { useGetUrlInflater } from "@applicaster/zapp-react-native-utils/reactHooks/feed/useInflatedUrl";
|
|
17
20
|
|
|
18
21
|
const BRIGHTNESS_THRESHOLD = 160;
|
|
19
22
|
const ABOVE_DEFAULT_COLOR = "gray";
|
|
@@ -62,31 +65,51 @@ export const usePullToRefresh = (
|
|
|
62
65
|
|
|
63
66
|
const [refreshing, setRefreshing] = React.useState(false);
|
|
64
67
|
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
const feedSources = React.useMemo(
|
|
69
|
+
() =>
|
|
70
|
+
(riverComponents || [])
|
|
71
|
+
.map((riverComponent) => ({
|
|
72
|
+
source: path(["data", "source"], riverComponent),
|
|
73
|
+
mapping: path(["data", "mapping"], riverComponent),
|
|
74
|
+
}))
|
|
75
|
+
.filter(({ source }) => !isNilOrEmpty(source)),
|
|
76
|
+
[riverComponents]
|
|
77
|
+
);
|
|
68
78
|
|
|
69
|
-
const feedsLength =
|
|
79
|
+
const feedsLength = feedSources.length;
|
|
70
80
|
|
|
71
81
|
const [requestsCompletedCounter, setRequestsCompletedCounter] =
|
|
72
82
|
React.useState(0);
|
|
73
83
|
|
|
74
84
|
const loadPipesDataDispatcher = useLoadPipesDataDispatch();
|
|
85
|
+
const urlInflater = useGetUrlInflater();
|
|
75
86
|
|
|
76
87
|
React.useEffect(() => {
|
|
77
88
|
// will not work for pipes v1 on 1st level screens
|
|
78
89
|
if (refreshing && !isPipesV1) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
feedSources.forEach(({ source, mapping }) => {
|
|
91
|
+
const inflatedUrl = urlInflater(source, mapping);
|
|
92
|
+
|
|
93
|
+
if (inflatedUrl) {
|
|
94
|
+
loadPipesDataDispatcher(inflatedUrl, {
|
|
95
|
+
silentRefresh: true,
|
|
96
|
+
clearCache: true,
|
|
97
|
+
callback: () => {
|
|
98
|
+
setRequestsCompletedCounter(R.inc);
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
} else {
|
|
102
|
+
setRequestsCompletedCounter(R.inc);
|
|
103
|
+
}
|
|
87
104
|
});
|
|
88
105
|
}
|
|
89
|
-
}, [
|
|
106
|
+
}, [
|
|
107
|
+
refreshing,
|
|
108
|
+
isPipesV1,
|
|
109
|
+
feedSources,
|
|
110
|
+
loadPipesDataDispatcher,
|
|
111
|
+
urlInflater,
|
|
112
|
+
]);
|
|
90
113
|
|
|
91
114
|
React.useEffect(() => {
|
|
92
115
|
if (requestsCompletedCounter === feedsLength) {
|