@applicaster/zapp-react-native-ui-components 16.0.0-alpha.1593888345 → 16.0.0-alpha.1701179921
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/LinkHandler/LinkHandler.tsx +2 -7
- package/Components/MasterCell/DefaultComponents/ActionButton.tsx +4 -1
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +19 -4
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +43 -1
- package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +2 -4
- package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +2 -3
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/__tests__/factory.test.ts +32 -0
- package/Components/MasterCell/SharedUI/CollapsibleTextContainer/index.ts +1 -2
- package/Components/ModalComponent/ActionsBottomSheet/ActionButton.tsx +4 -2
- package/Components/ModalComponent/ActionsBottomSheet/__tests__/openActionsBottomSheet.test.ts +38 -0
- package/Components/ModalComponent/ActionsBottomSheet/boundActionButton.tsx +5 -2
- package/Components/ModalComponent/ActionsBottomSheet/openActionsBottomSheet.ts +4 -1
- package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +58 -73
- package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +65 -56
- package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +140 -65
- package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +93 -46
- package/Components/ModalComponent/AudioPlayer/Components/NowPlayingHeaderSection.tsx +148 -32
- package/Components/ModalComponent/AudioPlayer/Components/__tests__/Item.test.tsx +113 -0
- package/Components/ModalComponent/AudioPlayer/Components/__tests__/NowPlayingHeaderSection.test.tsx +36 -0
- package/Components/ModalComponent/AudioPlayer/Components/index.ts +0 -2
- package/Components/ModalComponent/AudioPlayer/utils/__tests__/mergeStyles.test.ts +132 -6
- package/Components/ModalComponent/AudioPlayer/utils/mergeStyles.ts +202 -13
- package/Components/ModalComponent/BottomSheetModalContent.tsx +187 -73
- package/Components/ModalComponent/SortableList.tsx +36 -22
- package/Components/ModalComponent/TextInputContent.tsx +42 -7
- package/Components/ModalComponent/__tests__/BottomSheetModalContent.test.tsx +111 -6
- package/Components/ModalComponent/__tests__/TextInputContent.test.tsx +50 -1
- package/Components/ModalComponent/index.tsx +7 -9
- package/Components/ModalComponent/presets/__tests__/getCell.test.ts +24 -0
- package/Components/ModalComponent/presets/selectableCell.tsx +1 -1
- package/Components/ModalComponent/showTextInput.ts +11 -2
- package/Components/PlayerContainer/PlayerContainer.tsx +8 -1
- package/Components/River/ComponentsMap/ComponentsMap.tsx +10 -12
- package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +138 -75
- package/Components/River/__tests__/componentsMap.test.js +8 -3
- package/Components/Screen/__tests__/Screen.test.tsx +24 -0
- package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +2 -0
- package/Components/Screen/index.tsx +5 -0
- package/Components/ScreenRevealManager/withScreenRevealManager.tsx +1 -1
- package/Components/Transitioner/Transitioner.tsx +2 -3
- package/Components/Transitioner/__tests__/transitioner.test.js +83 -2
- package/Components/VideoLive/LiveImageManager.ts +8 -2
- package/Components/VideoLive/__tests__/LiveImageManager.test.ts +59 -0
- package/Contexts/ModalNavigationContext/__tests__/index.test.tsx +15 -0
- package/Contexts/ModalNavigationContext/index.tsx +2 -2
- package/assets/empty-Info.png +0 -0
- package/package.json +5 -5
- package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +0 -314
|
@@ -12,14 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import React from "react";
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
Platform,
|
|
18
|
-
Pressable,
|
|
19
|
-
StyleSheet,
|
|
20
|
-
Text,
|
|
21
|
-
View,
|
|
22
|
-
} from "react-native";
|
|
15
|
+
import { QBImage as Image } from "@applicaster/zapp-react-native-ui-components/Components/Image";
|
|
16
|
+
import { Platform, Pressable, StyleSheet, Text, View } from "react-native";
|
|
23
17
|
import {
|
|
24
18
|
MenuItem,
|
|
25
19
|
SelectionBehavior,
|
|
@@ -60,6 +54,8 @@ export type BackgroundConfiguration = {
|
|
|
60
54
|
paddingRight?: number;
|
|
61
55
|
paddingBottom?: number;
|
|
62
56
|
paddingLeft?: number;
|
|
57
|
+
highlightInsetLeft?: number;
|
|
58
|
+
highlightInsetRight?: number;
|
|
63
59
|
};
|
|
64
60
|
|
|
65
61
|
export type ImageConfiguration = {
|
|
@@ -209,9 +205,9 @@ const CONTAINER_SPEC_LARGE_IMAGE: ContainerSpec = {
|
|
|
209
205
|
|
|
210
206
|
const CONTAINER_SPEC_DEFAULT: ContainerSpec = {
|
|
211
207
|
paddingTop: 8,
|
|
212
|
-
paddingRight:
|
|
208
|
+
paddingRight: 10,
|
|
213
209
|
paddingBottom: 8,
|
|
214
|
-
paddingLeft:
|
|
210
|
+
paddingLeft: 10,
|
|
215
211
|
horizontalGutter: 12,
|
|
216
212
|
verticalGutter: 2,
|
|
217
213
|
};
|
|
@@ -291,13 +287,14 @@ function resolveContainerSpec(image: ImageConfiguration): ContainerSpec {
|
|
|
291
287
|
|
|
292
288
|
export function mapMenuItemToNowPlayingData(
|
|
293
289
|
item: MenuItem,
|
|
294
|
-
isPaused?: boolean
|
|
290
|
+
isPaused?: boolean,
|
|
291
|
+
nowPlayingLabel = "Now Playing"
|
|
295
292
|
): ItemData {
|
|
296
293
|
return {
|
|
297
294
|
imageUri: item.icon,
|
|
298
295
|
textLabel1: item.title,
|
|
299
296
|
textLabel2: item.summary ?? "",
|
|
300
|
-
nowPlayingLabel
|
|
297
|
+
nowPlayingLabel,
|
|
301
298
|
trailingButton: "playPause",
|
|
302
299
|
isSelected: isPaused !== undefined ? !isPaused : item.isSelected,
|
|
303
300
|
};
|
|
@@ -355,21 +352,30 @@ function ItemContainer({
|
|
|
355
352
|
onPress,
|
|
356
353
|
}: ItemContainerProps) {
|
|
357
354
|
const container = resolveContainerSpec(image);
|
|
358
|
-
const
|
|
355
|
+
const insetLeft = background.highlightInsetLeft ?? 0;
|
|
356
|
+
const insetRight = background.highlightInsetRight ?? 0;
|
|
359
357
|
|
|
360
358
|
return (
|
|
361
|
-
<Pressable
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
359
|
+
<Pressable onPress={onPress}>
|
|
360
|
+
{({ pressed }) => (
|
|
361
|
+
<View
|
|
362
|
+
style={{
|
|
363
|
+
marginLeft: insetLeft,
|
|
364
|
+
marginRight: insetRight,
|
|
365
|
+
backgroundColor:
|
|
366
|
+
pressed || focused
|
|
367
|
+
? background.focusedColor
|
|
368
|
+
: background.defaultColor,
|
|
369
|
+
borderRadius: background.cornerRadius,
|
|
370
|
+
paddingTop: background.paddingTop ?? container.paddingTop,
|
|
371
|
+
paddingRight: background.paddingRight ?? container.paddingRight,
|
|
372
|
+
paddingBottom: background.paddingBottom ?? container.paddingBottom,
|
|
373
|
+
paddingLeft: background.paddingLeft ?? container.paddingLeft,
|
|
374
|
+
}}
|
|
375
|
+
>
|
|
376
|
+
{children}
|
|
377
|
+
</View>
|
|
378
|
+
)}
|
|
373
379
|
</Pressable>
|
|
374
380
|
);
|
|
375
381
|
}
|
|
@@ -402,6 +408,10 @@ type ItemLabelProps = {
|
|
|
402
408
|
};
|
|
403
409
|
|
|
404
410
|
function ItemLabel({ text, configuration, focused = false }: ItemLabelProps) {
|
|
411
|
+
if (!text) {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
|
|
405
415
|
const color =
|
|
406
416
|
focused && configuration.focusedFontColor
|
|
407
417
|
? configuration.focusedFontColor
|
|
@@ -706,18 +716,31 @@ function ItemButton({
|
|
|
706
716
|
}
|
|
707
717
|
};
|
|
708
718
|
|
|
719
|
+
const buttonPadding = {
|
|
720
|
+
paddingTop: layout.paddingTop,
|
|
721
|
+
paddingRight: layout.paddingRight,
|
|
722
|
+
paddingBottom: layout.paddingBottom,
|
|
723
|
+
paddingLeft: layout.paddingLeft,
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
// A nested Pressable with no handler swallows the parent row press.
|
|
727
|
+
if (!onPress) {
|
|
728
|
+
return (
|
|
729
|
+
<View pointerEvents="none" style={buttonPadding}>
|
|
730
|
+
{renderIcon()}
|
|
731
|
+
</View>
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
|
|
709
735
|
return (
|
|
710
736
|
<Pressable
|
|
711
737
|
onPress={(e: any) => {
|
|
712
738
|
e?.stopPropagation?.();
|
|
713
|
-
onPress
|
|
739
|
+
onPress();
|
|
714
740
|
}}
|
|
715
741
|
style={({ pressed }) => [
|
|
742
|
+
buttonPadding,
|
|
716
743
|
{
|
|
717
|
-
paddingTop: layout.paddingTop,
|
|
718
|
-
paddingRight: layout.paddingRight,
|
|
719
|
-
paddingBottom: layout.paddingBottom,
|
|
720
|
-
paddingLeft: layout.paddingLeft,
|
|
721
744
|
opacity: pressed || focused ? 0.7 : 1,
|
|
722
745
|
},
|
|
723
746
|
]}
|
|
@@ -849,6 +872,10 @@ export function AudioPlayerTrackItem({
|
|
|
849
872
|
|
|
850
873
|
const container = resolveContainerSpec(image);
|
|
851
874
|
|
|
875
|
+
const handleTrailingButtonPress =
|
|
876
|
+
onTrailingButtonPress ??
|
|
877
|
+
(data.trailingButton === "multiSelect" ? onPress : undefined);
|
|
878
|
+
|
|
852
879
|
return (
|
|
853
880
|
<ItemContainer
|
|
854
881
|
image={image}
|
|
@@ -861,22 +888,28 @@ export function AudioPlayerTrackItem({
|
|
|
861
888
|
<View style={{ width: container.horizontalGutter }} />
|
|
862
889
|
<View style={styles.contentColumn}>
|
|
863
890
|
{showNowPlaying ? (
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
891
|
+
<NowPlayingRow
|
|
892
|
+
assetConfiguration={nowPlayingAsset}
|
|
893
|
+
labelConfiguration={nowPlayingLabel}
|
|
894
|
+
label={data.nowPlayingLabel}
|
|
895
|
+
/>
|
|
896
|
+
) : null}
|
|
897
|
+
{showNowPlaying && (data.textLabel1 || data.textLabel2) ? (
|
|
898
|
+
<View style={{ height: container.verticalGutter }} />
|
|
899
|
+
) : null}
|
|
900
|
+
{data.textLabel1 ? (
|
|
901
|
+
<ItemLabel
|
|
902
|
+
text={data.textLabel1}
|
|
903
|
+
configuration={textLabel1}
|
|
904
|
+
focused={focused}
|
|
905
|
+
/>
|
|
906
|
+
) : null}
|
|
907
|
+
{data.textLabel1 && data.textLabel2 ? (
|
|
908
|
+
<View style={{ height: container.verticalGutter }} />
|
|
909
|
+
) : null}
|
|
910
|
+
{data.textLabel2 ? (
|
|
911
|
+
<ItemLabel text={data.textLabel2} configuration={textLabel2} />
|
|
872
912
|
) : null}
|
|
873
|
-
<ItemLabel
|
|
874
|
-
text={data.textLabel1}
|
|
875
|
-
configuration={textLabel1}
|
|
876
|
-
focused={focused}
|
|
877
|
-
/>
|
|
878
|
-
<View style={{ height: container.verticalGutter }} />
|
|
879
|
-
<ItemLabel text={data.textLabel2} configuration={textLabel2} />
|
|
880
913
|
</View>
|
|
881
914
|
<ItemButtons
|
|
882
915
|
leadingButton={data.leadingButton}
|
|
@@ -884,7 +917,7 @@ export function AudioPlayerTrackItem({
|
|
|
884
917
|
focusedLeadingButton={focusedLeadingButton}
|
|
885
918
|
focusedTrailingButton={focusedTrailingButton}
|
|
886
919
|
onLeadingButtonPress={onLeadingButtonPress}
|
|
887
|
-
onTrailingButtonPress={
|
|
920
|
+
onTrailingButtonPress={handleTrailingButtonPress}
|
|
888
921
|
isSelected={data.isSelected}
|
|
889
922
|
renderHandle={renderHandle}
|
|
890
923
|
checkboxAssets={checkboxAssets}
|
|
@@ -905,6 +938,10 @@ export const VARIANT_NOW_PLAYING: ItemConfiguration = {
|
|
|
905
938
|
defaultColor: "transparent",
|
|
906
939
|
focusedColor: "transparent",
|
|
907
940
|
cornerRadius: 0,
|
|
941
|
+
paddingTop: 8,
|
|
942
|
+
paddingRight: 20,
|
|
943
|
+
paddingBottom: 8,
|
|
944
|
+
paddingLeft: 20,
|
|
908
945
|
},
|
|
909
946
|
image: {
|
|
910
947
|
width: 72,
|
|
@@ -925,6 +962,12 @@ export const VARIANT_QUEUED_ITEM: ItemConfiguration = {
|
|
|
925
962
|
defaultColor: "transparent",
|
|
926
963
|
focusedColor: "rgba(62, 62, 62, 1)",
|
|
927
964
|
cornerRadius: 12,
|
|
965
|
+
paddingTop: 8,
|
|
966
|
+
paddingRight: 10,
|
|
967
|
+
paddingBottom: 8,
|
|
968
|
+
paddingLeft: 10,
|
|
969
|
+
highlightInsetLeft: 10,
|
|
970
|
+
highlightInsetRight: 10,
|
|
928
971
|
},
|
|
929
972
|
image: {
|
|
930
973
|
width: 48,
|
|
@@ -945,6 +988,10 @@ export const VARIANT_PLAYLIST_ITEM: ItemConfiguration = {
|
|
|
945
988
|
defaultColor: "transparent",
|
|
946
989
|
focusedColor: "rgba(62, 62, 62, 1)",
|
|
947
990
|
cornerRadius: 12,
|
|
991
|
+
paddingTop: 8,
|
|
992
|
+
paddingRight: 10,
|
|
993
|
+
paddingBottom: 8,
|
|
994
|
+
paddingLeft: 10,
|
|
948
995
|
},
|
|
949
996
|
image: {
|
|
950
997
|
width: 48,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { StyleSheet, View } from "react-native";
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { StyleSheet, Text, View } from "react-native";
|
|
3
3
|
import {
|
|
4
4
|
AudioPlayerTrackItem,
|
|
5
5
|
VARIANT_NOW_PLAYING,
|
|
@@ -7,8 +7,11 @@ import {
|
|
|
7
7
|
} from "./Item";
|
|
8
8
|
import { AudioPlayerHeader, VARIANT_SECTION_HEADER } from "./Header";
|
|
9
9
|
import type { MenuItem } from "@applicaster/zapp-react-native-utils/modalState/types";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager";
|
|
11
|
+
import { PlayerRole } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/const";
|
|
12
|
+
import type { Player } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/player";
|
|
13
|
+
|
|
14
|
+
const DEFAULT_EMPTY_MESSAGE = "Queued items will be added here";
|
|
12
15
|
|
|
13
16
|
const styles = StyleSheet.create({
|
|
14
17
|
divider: {
|
|
@@ -17,27 +20,149 @@ const styles = StyleSheet.create({
|
|
|
17
20
|
marginHorizontal: 20,
|
|
18
21
|
marginVertical: 8,
|
|
19
22
|
},
|
|
23
|
+
emptyMessage: {
|
|
24
|
+
paddingTop: 32,
|
|
25
|
+
paddingBottom: 32,
|
|
26
|
+
paddingHorizontal: 40,
|
|
27
|
+
textAlign: "center",
|
|
28
|
+
color: "#999999",
|
|
29
|
+
fontSize: 13,
|
|
30
|
+
lineHeight: 16,
|
|
31
|
+
},
|
|
20
32
|
});
|
|
21
33
|
|
|
34
|
+
const isPrimaryOrCastingPlayer = (player?: Player | null): boolean => {
|
|
35
|
+
if (!player) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
player.playerRole === PlayerRole.Primary ||
|
|
41
|
+
player.playerRole === PlayerRole.Chromecast
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const getTargetPlayer = (): Player | null => {
|
|
46
|
+
if (playerManager.isCasting()) {
|
|
47
|
+
return playerManager.getCastingReceiver() || null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const active = playerManager.getActivePlayer();
|
|
51
|
+
|
|
52
|
+
return isPrimaryOrCastingPlayer(active) ? active : null;
|
|
53
|
+
};
|
|
54
|
+
|
|
22
55
|
type NowPlayingHeaderSectionProps = {
|
|
23
56
|
onClear?: () => void;
|
|
57
|
+
nextUpTitle?: string;
|
|
58
|
+
nowPlayingTitle?: string;
|
|
59
|
+
isEmpty?: boolean;
|
|
60
|
+
emptyMessage?: string;
|
|
61
|
+
clearLabel?: string;
|
|
24
62
|
};
|
|
25
63
|
|
|
26
64
|
export function NowPlayingHeaderSection({
|
|
27
65
|
onClear,
|
|
66
|
+
nextUpTitle = "Next Up",
|
|
67
|
+
nowPlayingTitle = "Now Playing",
|
|
68
|
+
isEmpty = false,
|
|
69
|
+
emptyMessage = DEFAULT_EMPTY_MESSAGE,
|
|
70
|
+
clearLabel,
|
|
28
71
|
}: NowPlayingHeaderSectionProps) {
|
|
29
|
-
const
|
|
30
|
-
|
|
72
|
+
const [activePlayer, setActivePlayer] = useState<Player | null>(
|
|
73
|
+
getTargetPlayer
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const [currentEntry, setCurrentEntry] = useState<ZappEntry | null>(
|
|
77
|
+
() => getTargetPlayer()?.entry || null
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const [isPaused, setIsPaused] = useState<boolean>(() =>
|
|
81
|
+
Boolean(getTargetPlayer()?.isPaused())
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
const handleActivePlayerUpdate = () => {
|
|
86
|
+
const player = getTargetPlayer();
|
|
87
|
+
|
|
88
|
+
if (player) {
|
|
89
|
+
setActivePlayer(player);
|
|
90
|
+
|
|
91
|
+
if (player.entry) {
|
|
92
|
+
setCurrentEntry(player.entry);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
setIsPaused(Boolean(player.isPaused()));
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
handleActivePlayerUpdate();
|
|
100
|
+
|
|
101
|
+
const removeLifecycle = playerManager.addLifecycleListener({
|
|
102
|
+
onRegistered: (registeredPlayer) => {
|
|
103
|
+
if (isPrimaryOrCastingPlayer(registeredPlayer)) {
|
|
104
|
+
handleActivePlayerUpdate();
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
onCastingChanged: handleActivePlayerUpdate,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return () => {
|
|
111
|
+
removeLifecycle?.();
|
|
112
|
+
};
|
|
113
|
+
}, []);
|
|
114
|
+
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (!activePlayer) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const syncPlayerState = () => {
|
|
121
|
+
if (activePlayer.entry) {
|
|
122
|
+
setCurrentEntry(activePlayer.entry);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
setIsPaused(Boolean(activePlayer.isPaused()));
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
syncPlayerState();
|
|
129
|
+
|
|
130
|
+
const removeListener = activePlayer.addListener({
|
|
131
|
+
id: "queue-now-playing-header",
|
|
132
|
+
listener: {
|
|
133
|
+
onPlayerPause: () => setIsPaused(true),
|
|
134
|
+
onPlayerResume: () => setIsPaused(false),
|
|
135
|
+
onVideoLoad: syncPlayerState,
|
|
136
|
+
onLoad: syncPlayerState,
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return () => {
|
|
141
|
+
removeListener?.();
|
|
142
|
+
};
|
|
143
|
+
}, [activePlayer]);
|
|
144
|
+
|
|
145
|
+
const handlePlayPause = () => {
|
|
146
|
+
if (!activePlayer) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (isPaused) {
|
|
151
|
+
activePlayer.play();
|
|
152
|
+
setIsPaused(false);
|
|
153
|
+
} else {
|
|
154
|
+
activePlayer.pause();
|
|
155
|
+
setIsPaused(true);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
31
158
|
|
|
32
159
|
const renderNowPlayingTrack = () => {
|
|
33
|
-
if (!
|
|
160
|
+
if (!currentEntry) {
|
|
34
161
|
return null;
|
|
35
162
|
}
|
|
36
163
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
const imageGroup = Array.isArray(mediaGroup)
|
|
40
|
-
? mediaGroup.find((g: any) => g.type === "image")
|
|
164
|
+
const imageGroup = Array.isArray(currentEntry.media_group)
|
|
165
|
+
? currentEntry.media_group.find((g: any) => g.type === "image")
|
|
41
166
|
: null;
|
|
42
167
|
|
|
43
168
|
const firstImage = Array.isArray(imageGroup?.media_item)
|
|
@@ -45,34 +170,24 @@ export function NowPlayingHeaderSection({
|
|
|
45
170
|
: imageGroup?.media_item?.src;
|
|
46
171
|
|
|
47
172
|
const nowPlayingItem: MenuItem = {
|
|
48
|
-
id: String(
|
|
49
|
-
title: (
|
|
50
|
-
summary: (
|
|
173
|
+
id: String(currentEntry.id ?? "now_playing"),
|
|
174
|
+
title: (currentEntry.title as string) || String(currentEntry.id),
|
|
175
|
+
summary: (currentEntry.summary as string) ?? "",
|
|
51
176
|
icon: firstImage,
|
|
52
177
|
};
|
|
53
178
|
|
|
54
179
|
const data = mapMenuItemToNowPlayingData(
|
|
55
180
|
nowPlayingItem,
|
|
56
|
-
|
|
181
|
+
isPaused,
|
|
182
|
+
nowPlayingTitle
|
|
57
183
|
);
|
|
58
184
|
|
|
59
|
-
const handlePlayPause = () => {
|
|
60
|
-
if (playerState?.isPaused) {
|
|
61
|
-
player?.play();
|
|
62
|
-
} else {
|
|
63
|
-
player?.pause();
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
185
|
return (
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
/>
|
|
74
|
-
<View style={styles.divider} />
|
|
75
|
-
</View>
|
|
186
|
+
<AudioPlayerTrackItem
|
|
187
|
+
configuration={VARIANT_NOW_PLAYING}
|
|
188
|
+
data={data}
|
|
189
|
+
onTrailingButtonPress={handlePlayPause}
|
|
190
|
+
/>
|
|
76
191
|
);
|
|
77
192
|
};
|
|
78
193
|
|
|
@@ -81,9 +196,10 @@ export function NowPlayingHeaderSection({
|
|
|
81
196
|
{renderNowPlayingTrack()}
|
|
82
197
|
<AudioPlayerHeader
|
|
83
198
|
configuration={VARIANT_SECTION_HEADER}
|
|
84
|
-
data={{ title:
|
|
199
|
+
data={{ title: nextUpTitle, clearLabel }}
|
|
85
200
|
onClear={onClear}
|
|
86
201
|
/>
|
|
202
|
+
{isEmpty ? <Text style={styles.emptyMessage}>{emptyMessage}</Text> : null}
|
|
87
203
|
</View>
|
|
88
204
|
);
|
|
89
205
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Pressable } from "react-native";
|
|
3
|
+
import { fireEvent, render, screen } from "@testing-library/react-native";
|
|
4
|
+
import {
|
|
5
|
+
AudioPlayerTrackItem,
|
|
6
|
+
VARIANT_PLAYLIST_ITEM,
|
|
7
|
+
mapMenuItemToNowPlayingData,
|
|
8
|
+
} from "../Item";
|
|
9
|
+
|
|
10
|
+
const playlistItemData = {
|
|
11
|
+
textLabel1: "Queue",
|
|
12
|
+
textLabel2: "",
|
|
13
|
+
trailingButton: "multiSelect" as const,
|
|
14
|
+
isSelected: false,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe("AudioPlayerTrackItem multiSelect checkbox", () => {
|
|
18
|
+
it("invokes onPress when the checkbox is pressed even if onTrailingButtonPress is omitted", () => {
|
|
19
|
+
const onPress = jest.fn();
|
|
20
|
+
|
|
21
|
+
render(
|
|
22
|
+
<AudioPlayerTrackItem
|
|
23
|
+
configuration={VARIANT_PLAYLIST_ITEM}
|
|
24
|
+
data={playlistItemData}
|
|
25
|
+
onPress={onPress}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const pressables = screen.UNSAFE_getAllByType(Pressable);
|
|
30
|
+
fireEvent.press(pressables[pressables.length - 1]);
|
|
31
|
+
|
|
32
|
+
expect(onPress).toHaveBeenCalledTimes(1);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("invokes onTrailingButtonPress instead of onPress when a dedicated trailing handler is provided", () => {
|
|
36
|
+
const onPress = jest.fn();
|
|
37
|
+
const onTrailingButtonPress = jest.fn();
|
|
38
|
+
|
|
39
|
+
render(
|
|
40
|
+
<AudioPlayerTrackItem
|
|
41
|
+
configuration={VARIANT_PLAYLIST_ITEM}
|
|
42
|
+
data={playlistItemData}
|
|
43
|
+
onPress={onPress}
|
|
44
|
+
onTrailingButtonPress={onTrailingButtonPress}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const pressables = screen.UNSAFE_getAllByType(Pressable);
|
|
49
|
+
fireEvent.press(pressables[pressables.length - 1]);
|
|
50
|
+
|
|
51
|
+
expect(onTrailingButtonPress).toHaveBeenCalledTimes(1);
|
|
52
|
+
expect(onPress).not.toHaveBeenCalled();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("invokes onPress when the row is pressed", () => {
|
|
56
|
+
const onPress = jest.fn();
|
|
57
|
+
|
|
58
|
+
render(
|
|
59
|
+
<AudioPlayerTrackItem
|
|
60
|
+
configuration={VARIANT_PLAYLIST_ITEM}
|
|
61
|
+
data={playlistItemData}
|
|
62
|
+
onPress={onPress}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
fireEvent.press(screen.UNSAFE_getAllByType(Pressable)[0]);
|
|
67
|
+
|
|
68
|
+
expect(onPress).toHaveBeenCalledTimes(1);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("renders only textLabel1 Text element when textLabel2 is missing or empty", () => {
|
|
72
|
+
const { UNSAFE_getAllByType, queryByText } = render(
|
|
73
|
+
<AudioPlayerTrackItem
|
|
74
|
+
configuration={VARIANT_PLAYLIST_ITEM}
|
|
75
|
+
data={{
|
|
76
|
+
textLabel1: "Only Title",
|
|
77
|
+
textLabel2: "",
|
|
78
|
+
}}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const { Text } = require("react-native");
|
|
83
|
+
const textNodes = UNSAFE_getAllByType(Text);
|
|
84
|
+
expect(textNodes).toHaveLength(1);
|
|
85
|
+
expect(queryByText("Only Title")).toBeTruthy();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("mapMenuItemToNowPlayingData", () => {
|
|
90
|
+
const item = {
|
|
91
|
+
id: "item_1",
|
|
92
|
+
title: "Track Title",
|
|
93
|
+
summary: "Artist Name",
|
|
94
|
+
icon: "https://example.com/cover.png",
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
it("uses default 'Now Playing' label when omitted", () => {
|
|
98
|
+
const data = mapMenuItemToNowPlayingData(item as any);
|
|
99
|
+
expect(data.nowPlayingLabel).toBe("Now Playing");
|
|
100
|
+
expect(data.textLabel1).toBe("Track Title");
|
|
101
|
+
expect(data.textLabel2).toBe("Artist Name");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("uses custom nowPlayingLabel when provided", () => {
|
|
105
|
+
const data = mapMenuItemToNowPlayingData(
|
|
106
|
+
item as any,
|
|
107
|
+
false,
|
|
108
|
+
"En cours de lecture"
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
expect(data.nowPlayingLabel).toBe("En cours de lecture");
|
|
112
|
+
});
|
|
113
|
+
});
|
package/Components/ModalComponent/AudioPlayer/Components/__tests__/NowPlayingHeaderSection.test.tsx
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react-native";
|
|
3
|
+
import { NowPlayingHeaderSection } from "../NowPlayingHeaderSection";
|
|
4
|
+
|
|
5
|
+
jest.mock(
|
|
6
|
+
"@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer",
|
|
7
|
+
() => ({
|
|
8
|
+
usePlayer: () => null,
|
|
9
|
+
})
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
jest.mock(
|
|
13
|
+
"@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerState",
|
|
14
|
+
() => ({
|
|
15
|
+
usePlayerState: () => ({ isPaused: true }),
|
|
16
|
+
})
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const EMPTY_COPY = "Queued items will be added here";
|
|
20
|
+
|
|
21
|
+
describe("NowPlayingHeaderSection empty queue", () => {
|
|
22
|
+
it("renders the empty message under Next Up when the list is empty", () => {
|
|
23
|
+
render(<NowPlayingHeaderSection isEmpty emptyMessage={EMPTY_COPY} />);
|
|
24
|
+
|
|
25
|
+
expect(screen.getByText("Next Up")).toBeTruthy();
|
|
26
|
+
expect(screen.getByText(EMPTY_COPY)).toBeTruthy();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("does not render the empty message when the list has items", () => {
|
|
30
|
+
render(
|
|
31
|
+
<NowPlayingHeaderSection isEmpty={false} emptyMessage={EMPTY_COPY} />
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
expect(screen.queryByText(EMPTY_COPY)).toBeNull();
|
|
35
|
+
});
|
|
36
|
+
});
|