@applicaster/zapp-react-native-ui-components 15.0.0-alpha.6351072502 → 15.0.0-alpha.6685355529

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.
@@ -5,6 +5,14 @@ import {
5
5
 
6
6
  import { CHROMECAST_PLUGIN_ID, YOUTUBE_PLUGIN_ID } from "./const";
7
7
  import { omit } from "@applicaster/zapp-react-native-utils/utils";
8
+ import { getXray } from "@applicaster/zapp-react-native-utils/logger";
9
+
10
+ const { Logger } = getXray();
11
+
12
+ const logger = new Logger(
13
+ "QuickBrick",
14
+ "packages/zapp-react-native-ui-components/Components/HandlePlayable"
15
+ );
8
16
 
9
17
  const getPlayerModuleProperties = (PlayerModule: ZappPlugin) => {
10
18
  if (PlayerModule?.Component && typeof PlayerModule.Component === "object") {
@@ -52,10 +60,25 @@ export const getPlayer = (
52
60
  if (type) {
53
61
  PlayerModule = findPluginByIdentifier(type, plugins)?.module;
54
62
 
63
+ if (!PlayerModule) {
64
+ logger.error({
65
+ message:
66
+ "PlayerModule is undefined – type mapping may be wrong or type not set for player",
67
+ data: {
68
+ type,
69
+ screen_id,
70
+ item_type_value: item?.type?.value,
71
+ },
72
+ });
73
+
74
+ return [null, {}];
75
+ }
76
+
55
77
  return getPlayerWithModuleProperties(PlayerModule);
56
78
  }
57
79
  }
58
80
 
81
+ // TODO: Probably should be removed, Youtube plugin is deprecated
59
82
  if (item?.content?.type === "youtube-id") {
60
83
  PlayerModule = findYoutubePlugin(plugins)?.module;
61
84
 
@@ -70,5 +93,13 @@ export const getPlayer = (
70
93
  )
71
94
  );
72
95
 
96
+ if (!PlayerModule) {
97
+ logger.error({
98
+ message: "PlayerModule is undefined – playable plugin not found",
99
+ });
100
+
101
+ return [null, {}];
102
+ }
103
+
73
104
  return getPlayerWithModuleProperties(PlayerModule);
74
105
  };
@@ -277,7 +277,6 @@ const PlayerContainerComponent = (props: Props) => {
277
277
  const { isRestricted } = useRestrictMobilePlayback({
278
278
  player,
279
279
  entry: item,
280
- pluginConfiguration,
281
280
  close,
282
281
  });
283
282
 
@@ -1,27 +1,49 @@
1
1
  import { Player } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/player";
2
2
  import NetInfo from "@react-native-community/netinfo";
3
3
 
4
- import { useEffect, useMemo, useRef, useState } from "react";
4
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
5
5
  import { showAlertDialog } from "@applicaster/zapp-react-native-utils/alertUtils";
6
6
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
7
7
  import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
8
+ import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager";
9
+ import { usePlugins } from "@applicaster/zapp-react-native-redux/hooks";
10
+ import { getLocalizations } from "@applicaster/zapp-react-native-utils/localizationUtils";
8
11
  import { log_info } from "./logger";
9
12
 
10
13
  type RestrictMobilePlaybackProps = {
11
14
  player?: Player;
12
15
  entry?: ZappEntry;
13
- pluginConfiguration?: Record<string, string>;
14
16
  close: () => void;
15
17
  };
16
18
 
17
19
  export const useRestrictMobilePlayback = ({
18
20
  player,
19
21
  entry,
20
- pluginConfiguration,
21
22
  close,
22
23
  }: RestrictMobilePlaybackProps): { isRestricted: boolean } => {
23
24
  const dialogVisibleRef = useRef<boolean>(false);
24
25
  const theme = useTheme();
26
+ const plugins = usePlugins();
27
+
28
+ const restrictMobilePlugin = useMemo(
29
+ () =>
30
+ plugins.find((p) => p.identifier === "quick-brick-hook-restrict-mobile"),
31
+ [plugins]
32
+ );
33
+
34
+ const localizations = useMemo(
35
+ () => restrictMobilePlugin?.configuration?.localizations,
36
+ [restrictMobilePlugin]
37
+ );
38
+
39
+ const localize = useCallback(
40
+ (key: string) => {
41
+ const l = getLocalizations({ localizations });
42
+
43
+ return (l && l[key]) || "";
44
+ },
45
+ [localizations]
46
+ );
25
47
 
26
48
  useEffect(() => {
27
49
  return () => {
@@ -38,8 +60,13 @@ export const useRestrictMobilePlayback = ({
38
60
  return false;
39
61
  }
40
62
 
63
+ // Only restrict if the plugin exists
64
+ if (!restrictMobilePlugin) {
65
+ return false;
66
+ }
67
+
41
68
  return player && entry?.extensions?.connection_restricted;
42
- }, [player, entry]);
69
+ }, [player, entry, restrictMobilePlugin]);
43
70
 
44
71
  const [isRestricted, setIsRestricted] = useState<boolean>(
45
72
  isConnectionRestricted
@@ -55,16 +82,17 @@ export const useRestrictMobilePlayback = ({
55
82
  "Stopping player due to mobile restriction, connection_restricted: true"
56
83
  );
57
84
 
58
- player?.close();
85
+ player?.closeNativePlayer();
86
+ playerManager?.invokeHandler("close");
59
87
 
60
88
  dialogVisibleRef.current = true;
61
89
 
62
90
  showAlertDialog({
63
91
  title:
64
- pluginConfiguration?.mobile_connection_restricted_alert_title ||
92
+ localize("restrict_mobile_playback_error_title") ||
65
93
  "Restricted Connection Type",
66
94
  message:
67
- pluginConfiguration?.mobile_connection_restricted_alert_message ||
95
+ localize("restrict_mobile_playback_error_message") ||
68
96
  "This content can only be viewed over a Wi-Fi or LAN network.",
69
97
  okButtonText: theme.ok_button || "OK",
70
98
  completion: () => {
@@ -91,7 +119,8 @@ export const useRestrictMobilePlayback = ({
91
119
  }, [
92
120
  close,
93
121
  entry?.extensions?.connection_restricted,
94
- pluginConfiguration,
122
+ localizations,
123
+ localize,
95
124
  player,
96
125
  theme.ok_button,
97
126
  isConnectionRestricted,
@@ -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>
@@ -137,6 +137,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
137
137
  keyExtractor={[Function]}
138
138
  maxToRenderPerBatch={10}
139
139
  onContentSizeChange={[Function]}
140
+ onEndReached={[Function]}
140
141
  onLayout={[Function]}
141
142
  onMomentumScrollBegin={[Function]}
142
143
  onMomentumScrollEnd={[Function]}
@@ -154,6 +155,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
154
155
  }
155
156
  }
156
157
  stickyHeaderIndices={[]}
158
+ testID="components-map-flat-list"
157
159
  viewabilityConfigCallbackPairs={[]}
158
160
  windowSize={12}
159
161
  >
@@ -139,7 +139,13 @@ jest.mock(
139
139
  })
140
140
  );
141
141
 
142
+ jest.mock("@applicaster/zapp-react-native-ui-components/events", () => ({
143
+ ...jest.requireActual("@applicaster/zapp-react-native-ui-components/events"),
144
+ emitScrollEndReached: jest.fn(),
145
+ }));
146
+
142
147
  const { View } = require("react-native");
148
+ const events = require("@applicaster/zapp-react-native-ui-components/events");
143
149
  const { ComponentsMap } = require("../ComponentsMap/ComponentsMap");
144
150
  const theme = require("./theme-mock.json");
145
151
 
@@ -190,4 +196,36 @@ describe("componentsMap", () => {
190
196
 
191
197
  expect(toJSON()).toMatchSnapshot();
192
198
  });
199
+
200
+ it("calls emitScrollEndReached when onScroll was called and isScreenWrappedInContainer is false", () => {
201
+ themeSpy = jest
202
+ .spyOn(themeUtils, "useTheme")
203
+ .mockImplementation(() => () => theme);
204
+
205
+ events.emitScrollEndReached.mockClear();
206
+
207
+ const { getByTestId } = render(
208
+ <Provider store={store}>
209
+ <ComponentsMap
210
+ {...props}
211
+ isScreenWrappedInContainer={false}
212
+ feed={{ entry: [] }}
213
+ />
214
+ </Provider>
215
+ );
216
+
217
+ const flatList = getByTestId("components-map-flat-list");
218
+
219
+ flatList.props.onScroll({
220
+ nativeEvent: {
221
+ contentOffset: { y: 0 },
222
+ layoutMeasurement: { height: 100 },
223
+ contentSize: { height: 200 },
224
+ },
225
+ });
226
+
227
+ flatList.props.onEndReached();
228
+
229
+ expect(events.emitScrollEndReached).toHaveBeenCalledTimes(1);
230
+ });
193
231
  });
package/events/index.ts CHANGED
@@ -7,3 +7,5 @@ export enum QBUIComponentEvents {
7
7
  focusOnSelectedTopMenuItem = "focusOnSelectedTopMenuItem",
8
8
  scrollToTopForScreenWrappedInContainer = "scrollToTopForScreenWrappedInContainer",
9
9
  }
10
+
11
+ export { scrollEndReached$, emitScrollEndReached } from "./scrollEndReached";
@@ -0,0 +1,15 @@
1
+ import { Subject } from "rxjs";
2
+ import { throttleTime } from "rxjs/operators";
3
+
4
+ const SCROLL_END_THROTTLE_MS = 1000;
5
+ const scrollEndReachedSubject = new Subject<void>();
6
+
7
+ /* Throttle so we only emit at most once per second; RN often fires onEndReached repeatedly (e.g. on Android) when near the bottom. */
8
+ export const scrollEndReached$ = scrollEndReachedSubject.pipe(
9
+ throttleTime(SCROLL_END_THROTTLE_MS)
10
+ );
11
+
12
+ /* Call from scroll container (ComponentsMap or Tabs) when scroll reaches end. */
13
+ export const emitScrollEndReached = (): void => {
14
+ scrollEndReachedSubject.next();
15
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-ui-components",
3
- "version": "15.0.0-alpha.6351072502",
3
+ "version": "15.0.0-alpha.6685355529",
4
4
  "description": "Applicaster Zapp React Native ui components for the Quick Brick App",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -28,10 +28,10 @@
28
28
  },
29
29
  "homepage": "https://github.com/applicaster/quickbrick#readme",
30
30
  "dependencies": {
31
- "@applicaster/applicaster-types": "15.0.0-alpha.6351072502",
32
- "@applicaster/zapp-react-native-bridge": "15.0.0-alpha.6351072502",
33
- "@applicaster/zapp-react-native-redux": "15.0.0-alpha.6351072502",
34
- "@applicaster/zapp-react-native-utils": "15.0.0-alpha.6351072502",
31
+ "@applicaster/applicaster-types": "15.0.0-alpha.6685355529",
32
+ "@applicaster/zapp-react-native-bridge": "15.0.0-alpha.6685355529",
33
+ "@applicaster/zapp-react-native-redux": "15.0.0-alpha.6685355529",
34
+ "@applicaster/zapp-react-native-utils": "15.0.0-alpha.6685355529",
35
35
  "fast-json-stable-stringify": "^2.1.0",
36
36
  "promise": "^8.3.0",
37
37
  "url": "^0.11.0",