@applicaster/zapp-react-native-ui-components 12.0.0-rc.14 → 12.0.0-rc.141
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/AudioPlayer.tsx +9 -1
- package/Components/AudioPlayer/__tests__/__snapshots__/audioPlayer.test.js.snap +5 -0
- package/Components/AudioPlayer/helpers.tsx +1 -0
- package/Components/BaseFocusable/index.tsx +3 -3
- package/Components/Cell/Cell.tsx +40 -12
- package/Components/Cell/CellWithFocusable.tsx +40 -19
- package/Components/Cell/TvOSCellComponent.tsx +5 -9
- package/Components/ErrorScreen/index.tsx +1 -1
- package/Components/Focusable/Focusable.tsx +6 -1
- package/Components/Focusable/__tests__/index.android.test.tsx +72 -0
- package/Components/Focusable/index.android.tsx +15 -9
- package/Components/FocusableCell/index.tsx +7 -7
- package/Components/FocusableGroup/index.tsx +11 -2
- package/Components/FocusableList/FocusableChild.ts +26 -16
- package/Components/FocusableList/FocusableItem.tsx +1 -1
- package/Components/FocusableList/FocusableListItemWrapper.tsx +76 -0
- package/Components/FocusableList/hooks/useCellState.android.ts +3 -1
- package/Components/FocusableList/index.tsx +37 -37
- package/Components/FocusableScrollView/index.tsx +1 -1
- package/Components/FreezeWithCallback/Freeze.tsx +31 -0
- package/Components/FreezeWithCallback/LICENSE +21 -0
- package/Components/FreezeWithCallback/Suspender.tsx +17 -0
- package/Components/FreezeWithCallback/__tests__/index.test.tsx +170 -0
- package/Components/FreezeWithCallback/index.tsx +3 -0
- package/Components/GeneralContentScreen/GeneralContentScreen.tsx +11 -8
- package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +13 -6
- package/Components/GeneralContentScreen/utils/useCurationAPI.ts +1 -1
- package/Components/HandlePlayable/HandlePlayable.tsx +4 -1
- package/Components/HookRenderer/index.tsx +1 -6
- package/Components/Image/Image.android.tsx +4 -26
- package/Components/Image/Image.tsx +18 -2
- package/Components/Image/utils/index.ts +39 -0
- package/Components/Layout/TV/ScreenContainer.tsx +79 -5
- package/Components/Layout/TV/ScreenLayoutContextProvider.tsx +2 -2
- package/Components/Layout/TV/__tests__/ScreenContainer.test.tsx +38 -28
- package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +25 -13
- package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +10 -50
- package/Components/Layout/TV/__tests__/index.test.tsx +20 -108
- package/Components/Layout/TV/index.tsx +15 -84
- package/Components/Layout/TV/index.web.tsx +33 -0
- package/Components/Layout/index.tsx +15 -11
- package/Components/MasterCell/DefaultComponents/ActionButton.tsx +14 -7
- package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +86 -117
- package/Components/MasterCell/DefaultComponents/CellBadge/index.tsx +27 -9
- package/Components/MasterCell/DefaultComponents/DynamicBadge.tsx +4 -2
- package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +16 -1
- package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +9 -26
- package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +3 -0
- package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +2 -2
- package/Components/MasterCell/DefaultComponents/Image/hooks/useImageSize.ts +65 -0
- package/Components/MasterCell/DefaultComponents/ImageBorderContainer/index.ts +71 -0
- package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -1
- package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +37 -2
- package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +3 -2
- package/Components/MasterCell/DefaultComponents/Text/index.tsx +14 -2
- package/Components/MasterCell/DefaultComponents/Toggle.tsx +5 -1
- package/Components/MasterCell/DefaultComponents/__tests__/text.test.tsx +24 -9
- package/Components/MasterCell/DefaultComponents/index.ts +2 -0
- package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +4 -1
- package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.tsx +10 -2
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/index.ts +6 -10
- package/Components/MasterCell/SharedUI/ProgressBar/hooks/useFillInPercent.ts +7 -1
- package/Components/MasterCell/dataAdapter.ts +21 -7
- package/Components/MasterCell/hooks/useAsyncRendering/index.ts +1 -1
- package/Components/MasterCell/index.tsx +5 -3
- package/Components/MasterCell/utils/__tests__/insertBetween.test.js +9 -0
- package/Components/MasterCell/utils/index.ts +31 -34
- package/Components/MeasurmentsPortal/BasicMeasurementsPortal.tsx +11 -7
- package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +10 -1
- package/Components/ModalComponent/BottomSheetModalContent.tsx +121 -0
- package/Components/ModalComponent/Button/Item.tsx +82 -0
- package/Components/ModalComponent/Button/ItemIcon.tsx +60 -0
- package/Components/ModalComponent/Button/ItemLabel.tsx +101 -0
- package/Components/ModalComponent/Button/assets.ts +4 -0
- package/Components/ModalComponent/Button/index.tsx +111 -0
- package/Components/ModalComponent/Header/CloseButton.tsx +59 -0
- package/Components/ModalComponent/Header/Title.tsx +70 -0
- package/Components/ModalComponent/Header/assets.ts +4 -0
- package/Components/ModalComponent/Header/index.tsx +71 -0
- package/Components/ModalComponent/Header/utils.ts +79 -0
- package/Components/ModalComponent/index.tsx +38 -0
- package/Components/ModalComponent/utils.ts +126 -0
- package/Components/Navigator/StackNavigator.tsx +57 -0
- package/Components/Navigator/index.ts +1 -0
- package/Components/PlayerContainer/PlayerContainer.tsx +80 -39
- package/Components/PlayerContainer/ProgramInfo/index.tsx +3 -1
- package/Components/PlayerImageBackground/index.tsx +34 -8
- package/Components/River/ComponentsMap/ComponentsMap.tsx +97 -2
- package/Components/River/RefreshControl.tsx +55 -1
- package/Components/River/TV/River.tsx +2 -25
- package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +1 -1
- package/Components/River/__tests__/componentsMap.test.js +7 -0
- package/Components/River/useScreenConfiguration.ts +2 -2
- package/Components/RouteManager/__tests__/__snapshots__/routeManager.test.js.snap +1 -0
- package/Components/RouteManager/__tests__/routeManager.test.js +9 -2
- package/Components/RouteManager/index.tsx +4 -2
- package/Components/Screen/TV/hooks/index.ts +1 -0
- package/Components/Screen/TV/hooks/useInitialFocus.ts +70 -0
- package/Components/Screen/TV/index.web.tsx +178 -0
- package/Components/Screen/__tests__/Screen.test.tsx +43 -75
- package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +24 -6
- package/Components/Screen/__tests__/navigationHandler.test.ts +1 -178
- package/Components/Screen/index.tsx +104 -65
- package/Components/Screen/navigationHandler.ts +6 -85
- package/Components/Screen/orientationHandler.ts +18 -7
- package/Components/Screen/utils.ts +16 -0
- package/Components/ScreenResolver/index.tsx +10 -13
- package/Components/Tabs/TV/styles.ts +1 -1
- package/Components/TrackedView/index.tsx +6 -3
- package/Components/Transitioner/Scene.tsx +39 -26
- package/Components/Transitioner/Transitioner.tsx +56 -48
- package/Components/Transitioner/__tests__/Scene.test.js +7 -0
- package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +12 -4
- package/Components/Transitioner/__tests__/__snapshots__/transitioner.test.js.snap +2 -0
- package/Components/VideoLive/LiveImageManager.ts +65 -11
- package/Components/VideoLive/PlayerLiveImageComponent.tsx +132 -95
- package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +45 -3
- package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +24 -27
- package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +60 -0
- package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +401 -0
- package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +172 -0
- package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +475 -0
- package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +182 -0
- package/Components/VideoModal/ModalAnimation/index.ts +15 -0
- package/Components/VideoModal/ModalAnimation/utils.ts +302 -0
- package/Components/VideoModal/PlayerDetails.tsx +21 -4
- package/Components/VideoModal/PlayerWrapper.tsx +115 -55
- package/Components/VideoModal/VideoModal.tsx +32 -43
- package/Components/VideoModal/__tests__/PlayerDetails.test.tsx +6 -1
- package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +12 -1
- package/Components/VideoModal/__tests__/__snapshots__/PlayerDetails.test.tsx.snap +2 -0
- package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +358 -166
- package/Components/VideoModal/hooks/useModalSize.ts +10 -12
- package/Components/VideoModal/utils.ts +8 -5
- package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +26 -21
- package/Components/Viewport/ViewportTracker/ViewportTracker.tsx +31 -40
- package/Components/Viewport/ViewportTracker/__tests__/viewportTracker.test.js +4 -5
- package/Components/Viewport/ViewportTracker/index.tsx +1 -0
- package/Components/Viewport/utils.js +1 -1
- package/Components/ZappFrameworkComponents/BarView/BarView.tsx +7 -3
- package/Components/ZappFrameworkComponents/BarView/Buttons/BackButton.tsx +7 -2
- package/Components/ZappFrameworkComponents/BarView/Buttons/CloseButton.tsx +7 -2
- package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +87 -0
- package/Components/ZappFrameworkComponents/Buttons/ActionButton.tsx +9 -4
- package/Components/ZappUIComponent/index.tsx +22 -12
- package/Components/default-cell-renderer/__tests__/__snapshots__/defaultCellRenderer.test.tsx.snap +4 -4
- package/Components/index.js +2 -0
- package/Contexts/CachedDimensionsContext/index.ts +13 -9
- package/Contexts/ConfirmDialogState/index.ts +1 -1
- package/Contexts/FocusableGroupContext/FocusableGroupContext.ts +3 -0
- package/Contexts/FocusableGroupContext/FocusableGroupContextProvider.tsx +16 -0
- package/Contexts/FocusableGroupContext/__tests__/FocusableGroupContextProvider.test.tsx +22 -0
- package/Contexts/FocusableGroupContext/__tests__/withFocusableContext.test.tsx +26 -0
- package/Contexts/FocusableGroupContext/index.ts +7 -0
- package/Contexts/FocusableGroupContext/withFocusableContext.tsx +17 -0
- package/Contexts/MenuToggleContext/{index.js → index.ts} +16 -0
- package/Contexts/ScreenContext/__tests__/__snapshots__/index.test.tsx.snap +3 -0
- package/Contexts/ScreenContext/__tests__/index.test.tsx +27 -0
- package/Contexts/ScreenContext/index.tsx +101 -30
- package/Contexts/ScreenDataContext/index.tsx +20 -0
- package/Contexts/ZappPipesContext/ZappPipesEntryContext.ts +1 -1
- package/Decorators/ComponentsMapWrapper/withComponentsMapProvider.tsx +7 -2
- package/const.ts +6 -0
- package/events/index.ts +1 -0
- package/index.js +3 -0
- package/package.json +5 -5
- package/Components/Layout/TV/TransitionOverlay.tsx +0 -64
- package/Components/VideoModal/hooks/usePosition.ts +0 -50
- /package/Components/FocusableGroup/{index.android.js → index.android.ts} +0 -0
- /package/Components/FocusableGroup/{index.ios.js → index.ios.ts} +0 -0
- /package/Contexts/{index.js → index.ts} +0 -0
|
@@ -54,6 +54,11 @@ import { toNumber } from "@applicaster/zapp-react-native-utils/numberUtils";
|
|
|
54
54
|
import { usePlayNextOverlay } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayNextOverlay";
|
|
55
55
|
import { PlayNextState } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/OverlayObserver/OverlaysObserver";
|
|
56
56
|
|
|
57
|
+
import {
|
|
58
|
+
useModalAnimationContext,
|
|
59
|
+
PlayerAnimationStateEnum,
|
|
60
|
+
} from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
|
|
61
|
+
|
|
57
62
|
type Props = {
|
|
58
63
|
Player: React.ComponentType<any>;
|
|
59
64
|
PlayerLoadingView?: React.ComponentType<any>; // 👀 we are not receiving this prop
|
|
@@ -63,6 +68,7 @@ type Props = {
|
|
|
63
68
|
mode?: VideoModalMode;
|
|
64
69
|
loadPipesData: (url: string, options: {}) => void;
|
|
65
70
|
disableCastAction?: boolean;
|
|
71
|
+
groupId?: string;
|
|
66
72
|
};
|
|
67
73
|
|
|
68
74
|
type ScreenStyles = ZappRiver["styles"] & {
|
|
@@ -118,6 +124,7 @@ const nativeStyles = {
|
|
|
118
124
|
playerScreen: {
|
|
119
125
|
flex: 1,
|
|
120
126
|
backgroundColor: "black",
|
|
127
|
+
overflow: "hidden",
|
|
121
128
|
},
|
|
122
129
|
playerWrapper: {
|
|
123
130
|
flex: 1,
|
|
@@ -145,34 +152,30 @@ const renderApplePlayer = ({
|
|
|
145
152
|
}) => {
|
|
146
153
|
const { title, summary } = item || {};
|
|
147
154
|
|
|
148
|
-
if (!
|
|
155
|
+
if (!isAppleTV() || !player) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (isAudioContent) {
|
|
160
|
+
return (
|
|
161
|
+
<AudioPlayer
|
|
162
|
+
audio_item={item}
|
|
163
|
+
plugin_configuration={pluginConfiguration}
|
|
164
|
+
/>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!playNextData && player.isPaused() && player.isAd() === false) {
|
|
149
169
|
return null;
|
|
150
170
|
}
|
|
151
171
|
|
|
152
172
|
return (
|
|
153
|
-
|
|
154
|
-
{
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
<ProgramInfo
|
|
160
|
-
title={title}
|
|
161
|
-
subtitle={summary}
|
|
162
|
-
plugin_configuration={pluginConfiguration}
|
|
163
|
-
videoStyle={videoStyle}
|
|
164
|
-
/>
|
|
165
|
-
)}
|
|
166
|
-
</>
|
|
167
|
-
)}
|
|
168
|
-
|
|
169
|
-
{isAudioContent && (
|
|
170
|
-
<AudioPlayer
|
|
171
|
-
audio_item={item}
|
|
172
|
-
plugin_configuration={pluginConfiguration}
|
|
173
|
-
/>
|
|
174
|
-
)}
|
|
175
|
-
</>
|
|
173
|
+
<ProgramInfo
|
|
174
|
+
title={title}
|
|
175
|
+
subtitle={summary}
|
|
176
|
+
plugin_configuration={pluginConfiguration}
|
|
177
|
+
videoStyle={videoStyle}
|
|
178
|
+
/>
|
|
176
179
|
);
|
|
177
180
|
};
|
|
178
181
|
|
|
@@ -199,6 +202,7 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
199
202
|
style: videoStyle,
|
|
200
203
|
mode = VideoModalMode.MAXIMIZED,
|
|
201
204
|
disableCastAction = false,
|
|
205
|
+
groupId,
|
|
202
206
|
} = props;
|
|
203
207
|
|
|
204
208
|
// Hooks
|
|
@@ -233,6 +237,9 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
233
237
|
const screenData = useTargetScreenData(item);
|
|
234
238
|
const { setVisible: showNavBar } = useSetNavbarState();
|
|
235
239
|
|
|
240
|
+
const { isActiveGesture, startComponentsAnimation, setPlayerAnimationState } =
|
|
241
|
+
useModalAnimationContext();
|
|
242
|
+
|
|
236
243
|
const playerEvent = (event, ...args) => {
|
|
237
244
|
playerManager.invokeHandler(event, ...args);
|
|
238
245
|
};
|
|
@@ -245,8 +252,8 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
245
252
|
return;
|
|
246
253
|
}
|
|
247
254
|
|
|
248
|
-
navigator.goBack();
|
|
249
255
|
showNavBar(true);
|
|
256
|
+
navigator.goBack();
|
|
250
257
|
}, [isModal, navigator.goBack, showNavBar]);
|
|
251
258
|
|
|
252
259
|
const playEntry = (entry) => navigator.replaceTop(entry, { mode });
|
|
@@ -339,17 +346,31 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
339
346
|
}
|
|
340
347
|
}, [item?.id, item?.extensions?.resumeTime, player]);
|
|
341
348
|
|
|
349
|
+
const onVideoFullscreenPlayerWillPresent = React.useCallback(() => {
|
|
350
|
+
playerManager.invokeHandler("toggleFullscreen", {
|
|
351
|
+
fullscreen: true,
|
|
352
|
+
});
|
|
353
|
+
}, []);
|
|
354
|
+
|
|
355
|
+
const onVideoFullscreenPlayerWillDismiss = React.useCallback(() => {
|
|
356
|
+
playerManager.invokeHandler("toggleFullscreen", {
|
|
357
|
+
fullscreen: false,
|
|
358
|
+
});
|
|
359
|
+
}, []);
|
|
360
|
+
|
|
342
361
|
// Util methods
|
|
343
362
|
const toggleFullscreen = (event) => {
|
|
344
363
|
const { fullscreen = false } = event || {};
|
|
345
364
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
365
|
+
setTimeout(() => {
|
|
366
|
+
if (fullscreen) {
|
|
367
|
+
navigator.fullscreenVideoModal();
|
|
368
|
+
playerEvent("onFullscreenPlayerWillPresent");
|
|
369
|
+
} else {
|
|
370
|
+
navigator.maximiseVideoModal();
|
|
371
|
+
playerEvent("onFullscreenPlayerWillDismiss");
|
|
372
|
+
}
|
|
373
|
+
});
|
|
353
374
|
};
|
|
354
375
|
|
|
355
376
|
const onCellTap = (entry) => {
|
|
@@ -381,17 +402,27 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
381
402
|
id: playerContainerId,
|
|
382
403
|
listener: {
|
|
383
404
|
onVideoEnd,
|
|
384
|
-
onError
|
|
385
|
-
onLoad
|
|
405
|
+
onError,
|
|
406
|
+
onLoad,
|
|
386
407
|
onPlayerClose: close,
|
|
387
408
|
onPlayerDetached: close,
|
|
409
|
+
onVideoFullscreenPlayerWillDismiss,
|
|
410
|
+
onVideoFullscreenPlayerWillPresent,
|
|
388
411
|
},
|
|
389
412
|
});
|
|
390
413
|
|
|
391
414
|
return () => {
|
|
392
415
|
player.removeListener(playerContainerId);
|
|
393
416
|
};
|
|
394
|
-
}, [
|
|
417
|
+
}, [
|
|
418
|
+
player,
|
|
419
|
+
onVideoEnd,
|
|
420
|
+
onError,
|
|
421
|
+
onLoad,
|
|
422
|
+
close,
|
|
423
|
+
onVideoFullscreenPlayerWillDismiss,
|
|
424
|
+
onVideoFullscreenPlayerWillPresent,
|
|
425
|
+
]);
|
|
395
426
|
|
|
396
427
|
useEffect(() => {
|
|
397
428
|
// TODO: We should not use playerManager here to connect buttons to player container
|
|
@@ -428,12 +459,16 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
428
459
|
if (disableMiniPlayer) {
|
|
429
460
|
navigator.closeVideoModal();
|
|
430
461
|
} else {
|
|
431
|
-
|
|
462
|
+
setPlayerAnimationState(PlayerAnimationStateEnum.minimize);
|
|
432
463
|
}
|
|
433
464
|
}
|
|
434
465
|
|
|
435
466
|
if (mode === VideoModalMode.FULLSCREEN) {
|
|
436
467
|
toggleFullscreen({ fullscreen: false });
|
|
468
|
+
|
|
469
|
+
if (!isModal) {
|
|
470
|
+
playerManager.closeNativePlayer();
|
|
471
|
+
}
|
|
437
472
|
}
|
|
438
473
|
|
|
439
474
|
return !!(
|
|
@@ -490,7 +525,7 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
490
525
|
}
|
|
491
526
|
|
|
492
527
|
return () => {
|
|
493
|
-
playerEvent("player_did_close");
|
|
528
|
+
playerEvent("player_did_close", { item });
|
|
494
529
|
|
|
495
530
|
if (!isModal) {
|
|
496
531
|
showNavBar(true);
|
|
@@ -603,6 +638,7 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
603
638
|
style={styles.focusableGroup}
|
|
604
639
|
preferredFocus
|
|
605
640
|
shouldUsePreferredFocus
|
|
641
|
+
groupId={groupId}
|
|
606
642
|
>
|
|
607
643
|
{/* Video player and components */}
|
|
608
644
|
<View
|
|
@@ -621,9 +657,13 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
621
657
|
}}
|
|
622
658
|
focused={isInlineTV ? true : undefined}
|
|
623
659
|
autoplay={true}
|
|
624
|
-
controls={
|
|
660
|
+
controls={false}
|
|
625
661
|
disableCastAction={disableCastAction}
|
|
626
|
-
docked={
|
|
662
|
+
docked={
|
|
663
|
+
navigator.isVideoModalDocked() &&
|
|
664
|
+
!startComponentsAnimation &&
|
|
665
|
+
!isActiveGesture
|
|
666
|
+
}
|
|
627
667
|
entry={item}
|
|
628
668
|
fullscreen={mode === VideoModalMode.FULLSCREEN}
|
|
629
669
|
inline={inline}
|
|
@@ -641,6 +681,7 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
641
681
|
setNextVideoPreloadThresholdPercentage={
|
|
642
682
|
setNextVideoPreloadThresholdPercentage
|
|
643
683
|
}
|
|
684
|
+
startComponentsAnimation={startComponentsAnimation}
|
|
644
685
|
>
|
|
645
686
|
{renderApplePlayer(applePlayerProps)}
|
|
646
687
|
</Player>
|
|
@@ -4,7 +4,9 @@ import { styleKeys } from "@applicaster/zapp-react-native-utils/styleKeysUtils";
|
|
|
4
4
|
import { transformColorCode as fixColorHexCode } from "@applicaster/zapp-react-native-utils/transform";
|
|
5
5
|
import * as R from "ramda";
|
|
6
6
|
import * as React from "react";
|
|
7
|
-
import {
|
|
7
|
+
import { Text, TextStyle, View, ViewStyle } from "react-native";
|
|
8
|
+
import { QBImage as Image } from "@applicaster/zapp-react-native-ui-components/Components/Image";
|
|
9
|
+
|
|
8
10
|
import * as programStyles from "./styles";
|
|
9
11
|
|
|
10
12
|
type Props = {
|
|
@@ -1,38 +1,64 @@
|
|
|
1
1
|
import React, { PropsWithChildren } from "react";
|
|
2
|
-
import { ImageBackground } from "react-native";
|
|
2
|
+
import { ImageBackground, View } from "react-native";
|
|
3
3
|
|
|
4
4
|
import { imageSrcFromMediaItem } from "@applicaster/zapp-react-native-utils/configurationUtils";
|
|
5
|
+
import {
|
|
6
|
+
AnimationComponent,
|
|
7
|
+
ComponentAnimationType,
|
|
8
|
+
useModalAnimationContext,
|
|
9
|
+
PlayerAnimationStateEnum,
|
|
10
|
+
} from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
|
|
5
11
|
|
|
6
12
|
type Props = PropsWithChildren<{
|
|
7
13
|
entry: ZappEntry;
|
|
8
14
|
style?: { [K: string]: any };
|
|
9
15
|
imageStyle?: { [K: string]: any };
|
|
10
16
|
imageKey?: string;
|
|
17
|
+
defaultImageDimensions?: { [K: string]: any };
|
|
11
18
|
}>;
|
|
12
19
|
|
|
20
|
+
const imageSize = { width: "100%", height: "100%" };
|
|
21
|
+
|
|
13
22
|
const PlayerImageBackgroundComponent = ({
|
|
14
23
|
entry,
|
|
15
24
|
children,
|
|
16
25
|
style,
|
|
17
26
|
imageStyle,
|
|
18
27
|
imageKey,
|
|
28
|
+
defaultImageDimensions,
|
|
19
29
|
}: Props) => {
|
|
20
30
|
const source = React.useMemo(
|
|
21
31
|
() => ({ uri: imageSrcFromMediaItem(entry, imageKey) }),
|
|
22
32
|
[imageKey, entry]
|
|
23
33
|
);
|
|
24
34
|
|
|
35
|
+
const { playerAnimationState } = useModalAnimationContext();
|
|
36
|
+
|
|
25
37
|
if (!source) return <>{children}</>;
|
|
26
38
|
|
|
27
39
|
return (
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
<View
|
|
41
|
+
style={
|
|
42
|
+
playerAnimationState === PlayerAnimationStateEnum.maximaze
|
|
43
|
+
? defaultImageDimensions
|
|
44
|
+
: style
|
|
45
|
+
}
|
|
33
46
|
>
|
|
34
|
-
|
|
35
|
-
|
|
47
|
+
<AnimationComponent
|
|
48
|
+
style={style}
|
|
49
|
+
animationType={ComponentAnimationType.player}
|
|
50
|
+
additionalData={defaultImageDimensions}
|
|
51
|
+
>
|
|
52
|
+
<ImageBackground
|
|
53
|
+
resizeMode="cover"
|
|
54
|
+
style={imageSize}
|
|
55
|
+
imageStyle={imageStyle}
|
|
56
|
+
source={source}
|
|
57
|
+
>
|
|
58
|
+
{children}
|
|
59
|
+
</ImageBackground>
|
|
60
|
+
</AnimationComponent>
|
|
61
|
+
</View>
|
|
36
62
|
);
|
|
37
63
|
};
|
|
38
64
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import * as R from "ramda";
|
|
3
|
-
import { View,
|
|
3
|
+
import { View, StyleSheet, Platform, FlatList } from "react-native";
|
|
4
4
|
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
5
5
|
import { RiverItem } from "../RiverItem";
|
|
6
6
|
import { RiverFooter } from "../RiverFooter";
|
|
@@ -21,6 +21,8 @@ import {
|
|
|
21
21
|
import { useScreenData } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenData";
|
|
22
22
|
import { isLast } from "@applicaster/zapp-react-native-utils/arrayUtils";
|
|
23
23
|
import { withComponentsMapProvider } from "@applicaster/zapp-react-native-ui-components/Decorators/ComponentsMapWrapper";
|
|
24
|
+
import { useScreenContextV2 } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
|
|
25
|
+
import { useShallow } from "zustand/react/shallow";
|
|
24
26
|
|
|
25
27
|
type Props = {
|
|
26
28
|
feed: ZappFeed;
|
|
@@ -40,8 +42,10 @@ const styles = StyleSheet.create({
|
|
|
40
42
|
},
|
|
41
43
|
});
|
|
42
44
|
|
|
43
|
-
const
|
|
45
|
+
const getNearestValue = (value, optionA, optionB) =>
|
|
46
|
+
Math.abs(value - optionA) < Math.abs(value - optionB) ? optionA : optionB;
|
|
44
47
|
|
|
48
|
+
const scrollIndicatorInsets = { right: 1 };
|
|
45
49
|
const keyExtractor = R.compose(String, R.prop("id"));
|
|
46
50
|
|
|
47
51
|
function ComponentsMapComponent(props: Props) {
|
|
@@ -59,6 +63,7 @@ function ComponentsMapComponent(props: Props) {
|
|
|
59
63
|
refreshingPipesV1,
|
|
60
64
|
} = props;
|
|
61
65
|
|
|
66
|
+
const flatListRef = React.useRef<FlatList | null>(null);
|
|
62
67
|
const screenConfig = useScreenConfiguration(riverId);
|
|
63
68
|
const screenData = useScreenData(riverId);
|
|
64
69
|
const pullToRefreshEnabled = screenData?.rules?.pull_to_refresh_enabled;
|
|
@@ -193,6 +198,88 @@ function ComponentsMapComponent(props: Props) {
|
|
|
193
198
|
[]
|
|
194
199
|
);
|
|
195
200
|
|
|
201
|
+
const navBarStore = useScreenContextV2()._navBarStore;
|
|
202
|
+
|
|
203
|
+
const scrollY = navBarStore(
|
|
204
|
+
useShallow(({ scrollYAnimated }) => scrollYAnimated)
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const canMomentum = React.useRef(false);
|
|
208
|
+
|
|
209
|
+
const onScrollBeginDrag = React.useCallback(() => {
|
|
210
|
+
canMomentum.current = true;
|
|
211
|
+
}, []);
|
|
212
|
+
|
|
213
|
+
/* onMomentumScrollEnd: Handling snap to expanded/collapsed state ( for navbar ) */
|
|
214
|
+
const _onMomentumScrollEnd = React.useCallback(({ nativeEvent }) => {
|
|
215
|
+
if (!canMomentum.current) return;
|
|
216
|
+
canMomentum.current = false;
|
|
217
|
+
|
|
218
|
+
const { height: headerHeight, scrollState } = navBarStore.getState();
|
|
219
|
+
|
|
220
|
+
const offsetY = Math.max(0, nativeEvent.contentOffset.y);
|
|
221
|
+
|
|
222
|
+
if (
|
|
223
|
+
!(scrollState === 0 || scrollState === -headerHeight) &&
|
|
224
|
+
flatListRef.current
|
|
225
|
+
) {
|
|
226
|
+
const offset =
|
|
227
|
+
getNearestValue(Math.round(scrollState), -headerHeight, 0) ===
|
|
228
|
+
-headerHeight
|
|
229
|
+
? Math.round(offsetY + scrollState + headerHeight)
|
|
230
|
+
: Math.round(offsetY + scrollState);
|
|
231
|
+
|
|
232
|
+
flatListRef.current.scrollToOffset({
|
|
233
|
+
animated: true,
|
|
234
|
+
offset,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}, []);
|
|
238
|
+
|
|
239
|
+
/* onScrollEnd: Handling snap to expanded/collapsed state ( for navbar ) */
|
|
240
|
+
const _onScrollEndDrag = React.useCallback(({ nativeEvent }) => {
|
|
241
|
+
const { height: headerHeight, scrollState } = navBarStore.getState();
|
|
242
|
+
|
|
243
|
+
const offsetY = Math.max(0, nativeEvent.contentOffset.y);
|
|
244
|
+
|
|
245
|
+
if (
|
|
246
|
+
!(scrollState === 0 || scrollState === -headerHeight) &&
|
|
247
|
+
flatListRef.current &&
|
|
248
|
+
nativeEvent.velocity.y === 0
|
|
249
|
+
) {
|
|
250
|
+
const offset =
|
|
251
|
+
getNearestValue(Math.round(scrollState), -headerHeight, 0) ===
|
|
252
|
+
-headerHeight
|
|
253
|
+
? Math.round(offsetY + scrollState + headerHeight)
|
|
254
|
+
: Math.round(offsetY + scrollState);
|
|
255
|
+
|
|
256
|
+
flatListRef.current.scrollToOffset({
|
|
257
|
+
animated: true,
|
|
258
|
+
offset,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}, []);
|
|
262
|
+
|
|
263
|
+
const onScroll = React.useCallback((event) => {
|
|
264
|
+
const {
|
|
265
|
+
nativeEvent: {
|
|
266
|
+
contentOffset: { y },
|
|
267
|
+
layoutMeasurement: { height },
|
|
268
|
+
contentSize: { height: contentHeight },
|
|
269
|
+
},
|
|
270
|
+
} = event;
|
|
271
|
+
|
|
272
|
+
if (y >= 0 && y + height <= contentHeight) {
|
|
273
|
+
scrollY.setValue(y);
|
|
274
|
+
} else {
|
|
275
|
+
if (y <= 0) {
|
|
276
|
+
scrollY.setValue(0);
|
|
277
|
+
} else if (y + height > contentHeight) {
|
|
278
|
+
scrollY.setValue(contentHeight - height);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}, []);
|
|
282
|
+
|
|
196
283
|
if (shouldShowLoadingError) {
|
|
197
284
|
return <RiverError />;
|
|
198
285
|
}
|
|
@@ -208,6 +295,9 @@ function ComponentsMapComponent(props: Props) {
|
|
|
208
295
|
>
|
|
209
296
|
<ViewportTracker>
|
|
210
297
|
<FlatList
|
|
298
|
+
ref={(ref) => {
|
|
299
|
+
flatListRef.current = ref;
|
|
300
|
+
}}
|
|
211
301
|
// Fix for WebView rerender crashes on Android API 28+
|
|
212
302
|
// https://github.com/react-native-webview/react-native-webview/issues/1915#issuecomment-964035468
|
|
213
303
|
overScrollMode={Platform.OS === "android" ? "never" : "auto"}
|
|
@@ -227,6 +317,11 @@ function ComponentsMapComponent(props: Props) {
|
|
|
227
317
|
}
|
|
228
318
|
ListFooterComponent={renderFooter}
|
|
229
319
|
refreshControl={refreshControl}
|
|
320
|
+
onScrollBeginDrag={onScrollBeginDrag}
|
|
321
|
+
onScroll={onScroll}
|
|
322
|
+
onMomentumScrollEnd={_onMomentumScrollEnd}
|
|
323
|
+
onScrollEndDrag={_onScrollEndDrag}
|
|
324
|
+
scrollEventThrottle={16}
|
|
230
325
|
{...scrollViewExtraProps}
|
|
231
326
|
/>
|
|
232
327
|
</ViewportTracker>
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Platform,
|
|
4
|
+
RefreshControl as RNRefreshControl,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
} from "react-native";
|
|
3
7
|
import * as R from "ramda";
|
|
4
8
|
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
5
9
|
import { useLocalizedStrings } from "@applicaster/zapp-react-native-utils/localizationUtils";
|
|
@@ -8,10 +12,30 @@ import { useSendAnalyticsEventWithFunction } from "@applicaster/zapp-react-nativ
|
|
|
8
12
|
import { useCurrentScreenData } from "@applicaster/zapp-react-native-utils/reactHooks/screen";
|
|
9
13
|
import { loadPipesData } from "@applicaster/zapp-react-native-redux/ZappPipes";
|
|
10
14
|
import { useDispatch } from "react-redux";
|
|
15
|
+
import { useShallow } from "zustand/react/shallow";
|
|
16
|
+
import { useScreenContextV2 } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
|
|
17
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
11
18
|
|
|
12
19
|
const BRIGHTNESS_THRESHOLD = 160;
|
|
13
20
|
const ABOVE_DEFAULT_COLOR = "gray";
|
|
14
21
|
const BELOW_DEFAULT_COLOR = "white";
|
|
22
|
+
const BASE_INDICATOR_HEIGHT = 40;
|
|
23
|
+
const BASE_INDICATOR_OFFSET = 16;
|
|
24
|
+
|
|
25
|
+
/** Return spacing required to position indicator below the navbar bottom line (Android only) */
|
|
26
|
+
const getRefreshControlOffset = () => {
|
|
27
|
+
return Platform.OS === "ios"
|
|
28
|
+
? 0
|
|
29
|
+
: BASE_INDICATOR_HEIGHT + BASE_INDICATOR_OFFSET;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const styles = StyleSheet.create({
|
|
33
|
+
// eslint-disable-next-line react-native/no-color-literals
|
|
34
|
+
control: {
|
|
35
|
+
zIndex: 1,
|
|
36
|
+
backgroundColor: "transparent",
|
|
37
|
+
},
|
|
38
|
+
});
|
|
15
39
|
|
|
16
40
|
const getBrightness = (RGBcolor) => {
|
|
17
41
|
const RGBvalues = RGBcolor.substring(
|
|
@@ -91,6 +115,34 @@ export const usePullToRefresh = (
|
|
|
91
115
|
};
|
|
92
116
|
};
|
|
93
117
|
|
|
118
|
+
/** Returns the offset for the progress view of the RefreshControl component
|
|
119
|
+
* based on navbar content position */
|
|
120
|
+
export const useGetProgressViewOffset = () => {
|
|
121
|
+
const { _navBarStore } = useScreenContextV2();
|
|
122
|
+
const { top } = useSafeAreaInsets();
|
|
123
|
+
|
|
124
|
+
const offset = getRefreshControlOffset();
|
|
125
|
+
|
|
126
|
+
const { height, contentPosition } = _navBarStore(
|
|
127
|
+
useShallow(({ contentPosition, height }) => ({ contentPosition, height }))
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
if (height === 0) {
|
|
131
|
+
return offset;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
switch (contentPosition) {
|
|
135
|
+
case "on_bottom_status_bar":
|
|
136
|
+
// navbar height
|
|
137
|
+
return offset + height;
|
|
138
|
+
case "on_top_screen":
|
|
139
|
+
// navbar height + status bar height
|
|
140
|
+
return offset + height + top;
|
|
141
|
+
default:
|
|
142
|
+
return offset;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
94
146
|
export function RefreshControl(props: {
|
|
95
147
|
pullToRefreshPipesV1RefreshingStateUpdater?: (refreshing: boolean) => void;
|
|
96
148
|
refreshingPipesV1?: boolean;
|
|
@@ -211,6 +263,7 @@ export function RefreshControl(props: {
|
|
|
211
263
|
refreshing={refreshing}
|
|
212
264
|
onRefresh={onRefreshHandler}
|
|
213
265
|
colors={[preparedIndicatorColor]}
|
|
266
|
+
progressViewOffset={useGetProgressViewOffset()}
|
|
214
267
|
progressBackgroundColor={preparedIndicatorBackgroundColor}
|
|
215
268
|
// @ts-ignore in RN68 this `size` became string
|
|
216
269
|
size={indicatorSize}
|
|
@@ -222,6 +275,7 @@ export function RefreshControl(props: {
|
|
|
222
275
|
: null
|
|
223
276
|
}
|
|
224
277
|
titleColor={preparedTitleUnderIndicatorColor}
|
|
278
|
+
style={styles.control}
|
|
225
279
|
/>
|
|
226
280
|
);
|
|
227
281
|
}
|
|
@@ -4,22 +4,18 @@ import * as React from "react";
|
|
|
4
4
|
import { Text } from "react-native";
|
|
5
5
|
import * as R from "ramda";
|
|
6
6
|
|
|
7
|
-
import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
|
|
8
7
|
import {
|
|
9
8
|
useFeedLoader,
|
|
10
9
|
useLayoutVersion,
|
|
11
|
-
useNavigationPluginData,
|
|
12
10
|
} from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
13
11
|
import { GeneralContentScreen } from "../../GeneralContentScreen";
|
|
14
12
|
import { ScreenResolver } from "@applicaster/zapp-react-native-ui-components/Components/ScreenResolver";
|
|
15
13
|
import { utilsLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
16
|
-
import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils";
|
|
17
|
-
import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
|
|
18
|
-
import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
19
14
|
import {
|
|
20
15
|
useNavbarState,
|
|
21
16
|
useSetNavbarState,
|
|
22
17
|
} from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
|
|
18
|
+
import { useRivers } from "@applicaster/zapp-react-native-utils/reactHooks/state";
|
|
23
19
|
|
|
24
20
|
type Props = {
|
|
25
21
|
screenId: string;
|
|
@@ -51,9 +47,7 @@ export const River = (props: Props) => {
|
|
|
51
47
|
const { setTitle: setScreenTitle, setSummary: setScreenSummary } =
|
|
52
48
|
useSetNavbarState();
|
|
53
49
|
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
const { rivers } = usePickFromState(["rivers"]);
|
|
50
|
+
const rivers = useRivers();
|
|
57
51
|
const isV2 = useLayoutVersion({ isV2: true });
|
|
58
52
|
|
|
59
53
|
const river = React.useMemo(() => rivers?.[screenId], [screenId]);
|
|
@@ -99,23 +93,6 @@ export const River = (props: Props) => {
|
|
|
99
93
|
}
|
|
100
94
|
}, [feedData, screenData, screenTitle, screenSummary]);
|
|
101
95
|
|
|
102
|
-
React.useEffect(() => {
|
|
103
|
-
if (
|
|
104
|
-
isWeb() &&
|
|
105
|
-
toBooleanWithDefaultFalse(
|
|
106
|
-
navigationPluginData?.rules?.focus_on_content
|
|
107
|
-
) &&
|
|
108
|
-
river &&
|
|
109
|
-
river.type !== "general_content"
|
|
110
|
-
) {
|
|
111
|
-
const groupId = R.pathOr("river", ["groupId"], componentsMapExtraProps);
|
|
112
|
-
|
|
113
|
-
focusManager.setupInitialFocus(groupId, true);
|
|
114
|
-
focusManager.setInitialFocus();
|
|
115
|
-
focusManager.enableAutomaticFocus();
|
|
116
|
-
}
|
|
117
|
-
}, [river, componentsMapExtraProps]);
|
|
118
|
-
|
|
119
96
|
if (!river) {
|
|
120
97
|
utilsLogger.error({
|
|
121
98
|
message: `River ${screenData.id} not found!`,
|
|
@@ -64,7 +64,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
|
|
|
64
64
|
refreshControl={null}
|
|
65
65
|
removeClippedSubviews={true}
|
|
66
66
|
renderItem={[Function]}
|
|
67
|
-
scrollEventThrottle={
|
|
67
|
+
scrollEventThrottle={16}
|
|
68
68
|
scrollIndicatorInsets={
|
|
69
69
|
Object {
|
|
70
70
|
"right": 1,
|
|
@@ -6,6 +6,12 @@ import configureStore from "redux-mock-store";
|
|
|
6
6
|
|
|
7
7
|
import * as themeUtils from "@applicaster/zapp-react-native-utils/theme";
|
|
8
8
|
|
|
9
|
+
// silences the errors related to React-native-gesture-handler in tests
|
|
10
|
+
jest.mock(
|
|
11
|
+
"@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation",
|
|
12
|
+
() => () => null
|
|
13
|
+
);
|
|
14
|
+
|
|
9
15
|
jest.mock("react-native-safe-area-context", () => ({
|
|
10
16
|
useSafeAreaInsets: () => ({ top: 50 }),
|
|
11
17
|
}));
|
|
@@ -83,6 +89,7 @@ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/navigation", () => ({
|
|
|
83
89
|
useNavigation: jest.fn(() => ({ currentRoute: "/river/A1234" })),
|
|
84
90
|
useRoute: jest.fn(() => ({ pathname: "/river/A1234", screenData })),
|
|
85
91
|
useProfilerLogging: jest.fn(),
|
|
92
|
+
useIsScreenActive: jest.fn().mockReturnValue(true),
|
|
86
93
|
}));
|
|
87
94
|
|
|
88
95
|
jest.mock("@applicaster/zapp-react-native-utils/reactHooks/screen", () => ({
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as R from "ramda";
|
|
2
2
|
|
|
3
|
-
import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
|
|
4
3
|
import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
5
4
|
import { toCamelCase } from "@applicaster/zapp-react-native-utils/stringUtils";
|
|
6
5
|
import { useMemo } from "react";
|
|
7
6
|
import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
7
|
+
import { useRivers } from "@applicaster/zapp-react-native-utils/reactHooks/state";
|
|
8
8
|
|
|
9
9
|
type GeneralContentScreen = ZappRiver & {
|
|
10
10
|
styles: {
|
|
@@ -46,7 +46,7 @@ const stylingKeys = [
|
|
|
46
46
|
];
|
|
47
47
|
|
|
48
48
|
export function useScreenConfiguration(screenId: string): ScreenConfiguration {
|
|
49
|
-
const
|
|
49
|
+
const rivers = useRivers();
|
|
50
50
|
const isTablet = useIsTablet();
|
|
51
51
|
|
|
52
52
|
return useMemo(() => {
|