@applicaster/zapp-react-native-ui-components 15.1.5 → 15.1.6-alpha.2015591222

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.
Files changed (24) hide show
  1. package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +33 -69
  2. package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +25 -47
  3. package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +122 -39
  4. package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +671 -0
  5. package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +299 -131
  6. package/Components/ModalComponent/AudioPlayer/Components/NowPlayingHeaderSection.tsx +86 -0
  7. package/Components/ModalComponent/AudioPlayer/Components/index.ts +8 -4
  8. package/Components/ModalComponent/BottomSheetModalContent.tsx +290 -55
  9. package/Components/ModalComponent/Button/index.tsx +5 -3
  10. package/Components/ModalComponent/Header/utils.ts +1 -1
  11. package/Components/ModalComponent/SortableList.tsx +142 -0
  12. package/Components/ModalComponent/TextInputContent.tsx +100 -0
  13. package/Components/ModalComponent/__tests__/BottomSheetModalContent.test.tsx +183 -0
  14. package/Components/ModalComponent/__tests__/TextInputContent.test.tsx +128 -0
  15. package/Components/ModalComponent/__tests__/showTextInput.test.ts +95 -0
  16. package/Components/ModalComponent/index.tsx +11 -0
  17. package/Components/ModalComponent/presets/__tests__/getCell.test.ts +66 -0
  18. package/Components/ModalComponent/presets/dynamicCollectionCell.tsx +74 -0
  19. package/Components/ModalComponent/presets/index.ts +27 -0
  20. package/Components/ModalComponent/presets/selectableCell.tsx +27 -0
  21. package/Components/ModalComponent/presets/standardCell.tsx +41 -0
  22. package/Components/ModalComponent/presets/types.ts +35 -0
  23. package/Components/ModalComponent/showTextInput.ts +43 -0
  24. package/package.json +6 -5
@@ -12,7 +12,18 @@
12
12
  */
13
13
 
14
14
  import React from "react";
15
- import { Image, Platform, StyleSheet, Text, View } from "react-native";
15
+ import {
16
+ Image,
17
+ Platform,
18
+ Pressable,
19
+ StyleSheet,
20
+ Text,
21
+ View,
22
+ } from "react-native";
23
+ import {
24
+ MenuItem,
25
+ SelectionBehavior,
26
+ } from "@applicaster/zapp-react-native-utils/modalState/types";
16
27
 
17
28
  const styles = StyleSheet.create({
18
29
  row: {
@@ -97,50 +108,6 @@ export type ItemConfiguration = {
97
108
  textLabel2: LabelConfiguration;
98
109
  };
99
110
 
100
- export interface Action {
101
- type: string;
102
- payload?: any;
103
- options?: any;
104
- }
105
-
106
- export interface Header {
107
- title: string;
108
- subtitle?: string;
109
- icon?: string;
110
- }
111
-
112
- export interface Content {
113
- title: string;
114
- stickyTitle?: boolean;
115
- items: MenuItem[];
116
- itemsUrl?: string;
117
- action?: Action;
118
- }
119
-
120
- export interface Menu {
121
- header?: Header;
122
- content: Content;
123
- closeButton?: boolean;
124
- }
125
-
126
- export interface PresentSubMenuAction extends Action {
127
- menu: Menu;
128
- }
129
-
130
- export interface SecondaryAction {
131
- action: Action | PresentSubMenuAction;
132
- icon?: string;
133
- }
134
-
135
- export interface MenuItem {
136
- title: string;
137
- summary?: string;
138
- icon?: string;
139
- isSelected?: boolean;
140
- action?: Action | PresentSubMenuAction;
141
- secondaryAction?: SecondaryAction;
142
- }
143
-
144
111
  export const DEFAULT_TEXT_LABEL_1_CONFIGURATION: LabelConfiguration = {
145
112
  fontColor: "#FFFFFF",
146
113
  focusedFontColor: "#FFFFFF",
@@ -191,14 +158,19 @@ export type ItemData = {
191
158
  leadingButton?: ItemButtonAction;
192
159
  /** Button 1 (right most) — hardcoded action decides icon. */
193
160
  trailingButton?: ItemButtonAction;
161
+ isSelected?: boolean;
194
162
  };
195
163
 
196
- export type ItemProps = {
164
+ export type AudioPlayerTrackItemProps = {
197
165
  configuration: ItemConfiguration;
198
166
  data: ItemData;
199
167
  focused?: boolean;
200
168
  focusedLeadingButton?: boolean;
201
169
  focusedTrailingButton?: boolean;
170
+ onPress?: () => void;
171
+ onLeadingButtonPress?: () => void;
172
+ onTrailingButtonPress?: () => void;
173
+ renderHandle?: (element: React.ReactElement) => React.ReactElement;
202
174
  };
203
175
 
204
176
  // ---------------------------------------------------------------------------
@@ -234,9 +206,9 @@ const CONTAINER_SPEC_LARGE_IMAGE: ContainerSpec = {
234
206
 
235
207
  const CONTAINER_SPEC_DEFAULT: ContainerSpec = {
236
208
  paddingTop: 8,
237
- paddingRight: 10,
209
+ paddingRight: 20,
238
210
  paddingBottom: 8,
239
- paddingLeft: 10,
211
+ paddingLeft: 20,
240
212
  horizontalGutter: 12,
241
213
  verticalGutter: 2,
242
214
  };
@@ -297,16 +269,6 @@ const DEFAULT_THUMB_ASSET =
297
269
  const DEFAULT_EQUALIZER_ASSET =
298
270
  "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAI0lEQVR42mNgGNTgPxTQzwB0DVQ3gKCBowbQwYAhkBIHBAAA9TvPMeOFaLMAAAAASUVORK5CYII=";
299
271
 
300
- const BUTTON_ASSETS: Record<ItemButtonAction, string> = {
301
- playPause:
302
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAAdklEQVR42u3ZwRUAERAEUfkn3TaF5TFTrSuCf3CgjZGg6ast7G8YaAlcB8JAj8F1MRR2G63CUNglNAqsRvmB1TAfsBoXcMABV4MFKEci4IADNrkAed2H80R66SHqvUvglh/ktoZcL5H7MHKBx/5xYH+RsP90t5t2UpF6+22a+AAAAABJRU5ErkJggg==",
303
- drag: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAIklEQVR42mNgGAWjYGiC/2SCwWPBaByMxsFoHIyCUTDYAAAqhk7AVXdZJwAAAABJRU5ErkJggg==",
304
- multiSelect:
305
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAWklEQVR42t2VUQ4AIAhCvf+lrQsULJJZbH7GM1cS0Uk5dd0Qqcz4GJSCSs0pSCkAHNoWBfkTgIxpgGK+ujyEKABqRGyn9FO1A+yfrf+qsGxTSx5YEs2WyU9oAKBJKg8Kfq4dAAAAAElFTkSuQmCC",
306
- remove:
307
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAUklEQVR42mNgGPbgPx5AVYOJlSPJcGqrJU8DKXooCVui9FIacXj1UyNl4DWDLhYgc8nA+INpeFgwtCOZ5vmALjmZ5mURXUpTutQHdKnRRgEMAACwjWWprU1B+AAAAABJRU5ErkJggg==",
308
- };
309
-
310
272
  function getTextTransformStyle(
311
273
  textTransform: TextTransform
312
274
  ): "none" | "lowercase" | "uppercase" | "capitalize" {
@@ -324,32 +286,43 @@ function resolveContainerSpec(image: ImageConfiguration): ContainerSpec {
324
286
  : CONTAINER_SPEC_DEFAULT;
325
287
  }
326
288
 
327
- function mapMenuItemToNowPlayingData(item: MenuItem): ItemData {
289
+ export function mapMenuItemToNowPlayingData(item: MenuItem): ItemData {
328
290
  return {
329
291
  imageUri: item.icon,
330
292
  textLabel1: item.title,
331
293
  textLabel2: item.summary ?? "",
332
294
  nowPlayingLabel: "Now Playing",
333
295
  trailingButton: "playPause",
296
+ isSelected: item.isSelected,
334
297
  };
335
298
  }
336
299
 
337
- function mapMenuItemToQueuedData(item: MenuItem): ItemData {
300
+ export function mapMenuItemToQueuedData(item: MenuItem): ItemData {
338
301
  return {
339
302
  imageUri: item.icon,
340
303
  textLabel1: item.title,
341
304
  textLabel2: item.summary ?? "",
342
305
  leadingButton: "remove",
343
306
  trailingButton: "drag",
307
+ isSelected: item.isSelected,
344
308
  };
345
309
  }
346
310
 
347
- function mapMenuItemToPlaylistData(item: MenuItem): ItemData {
311
+ export function mapMenuItemToPlaylistData(
312
+ item: MenuItem,
313
+ behavior?: SelectionBehavior
314
+ ): ItemData {
315
+ const showSelectionWidget =
316
+ behavior?.selectMode !== "none" && item.trailingButton !== "none";
317
+
348
318
  return {
349
319
  imageUri: item.icon,
350
320
  textLabel1: item.title,
351
321
  textLabel2: item.summary ?? "",
352
- trailingButton: "multiSelect",
322
+ trailingButton: showSelectionWidget
323
+ ? item.trailingButton || ("multiSelect" as ItemButtonAction)
324
+ : undefined,
325
+ isSelected: item.isSelected,
353
326
  };
354
327
  }
355
328
 
@@ -362,6 +335,7 @@ type ItemContainerProps = {
362
335
  background: BackgroundConfiguration;
363
336
  focused: boolean;
364
337
  children: React.ReactNode;
338
+ onPress?: () => void;
365
339
  };
366
340
 
367
341
  function ItemContainer({
@@ -369,23 +343,25 @@ function ItemContainer({
369
343
  background,
370
344
  focused,
371
345
  children,
346
+ onPress,
372
347
  }: ItemContainerProps) {
373
348
  const container = resolveContainerSpec(image);
374
349
  const bg = focused ? background.focusedColor : background.defaultColor;
375
350
 
376
351
  return (
377
- <View
378
- style={{
379
- backgroundColor: bg,
352
+ <Pressable
353
+ onPress={onPress}
354
+ style={({ pressed }) => ({
355
+ backgroundColor: pressed ? background.focusedColor : bg,
380
356
  borderRadius: background.cornerRadius,
381
357
  paddingTop: container.paddingTop,
382
358
  paddingRight: container.paddingRight,
383
359
  paddingBottom: container.paddingBottom,
384
360
  paddingLeft: container.paddingLeft,
385
- }}
361
+ })}
386
362
  >
387
363
  {children}
388
- </View>
364
+ </Pressable>
389
365
  );
390
366
  }
391
367
 
@@ -486,27 +462,212 @@ function NowPlayingRow({
486
462
  type ItemButtonProps = {
487
463
  action: ItemButtonAction;
488
464
  focused?: boolean;
465
+ onPress?: () => void;
466
+ isSelected?: boolean;
489
467
  };
490
468
 
491
- function ItemButton({ action, focused = false }: ItemButtonProps) {
469
+ function ItemButton({
470
+ action,
471
+ focused = false,
472
+ onPress,
473
+ isSelected = false,
474
+ }: ItemButtonProps) {
492
475
  const layout = BUTTON_LAYOUT_SPEC[action];
493
476
 
477
+ if (!layout) {
478
+ return null;
479
+ }
480
+
481
+ const renderIcon = () => {
482
+ switch (action) {
483
+ case "playPause": {
484
+ const iconSize = layout.assetWidth;
485
+ const iconRadius = iconSize / 2;
486
+
487
+ return (
488
+ <View
489
+ style={{
490
+ width: iconSize,
491
+ height: iconSize,
492
+ borderRadius: iconRadius,
493
+ backgroundColor: "#FFFFFF",
494
+ justifyContent: "center",
495
+ alignItems: "center",
496
+ }}
497
+ >
498
+ {isSelected ? (
499
+ <View
500
+ style={{
501
+ flexDirection: "row",
502
+ justifyContent: "space-between",
503
+ width: 10,
504
+ height: 16,
505
+ }}
506
+ >
507
+ <View
508
+ style={{
509
+ width: 3.5,
510
+ height: 16,
511
+ backgroundColor: "#1e1e1e",
512
+ borderRadius: 1,
513
+ }}
514
+ />
515
+ <View
516
+ style={{
517
+ width: 3.5,
518
+ height: 16,
519
+ backgroundColor: "#1e1e1e",
520
+ borderRadius: 1,
521
+ }}
522
+ />
523
+ </View>
524
+ ) : (
525
+ <View
526
+ style={{
527
+ width: 0,
528
+ height: 0,
529
+ backgroundColor: "transparent",
530
+ borderStyle: "solid",
531
+ borderLeftWidth: 14,
532
+ borderRightWidth: 0,
533
+ borderTopWidth: 8,
534
+ borderBottomWidth: 8,
535
+ borderLeftColor: "#1e1e1e",
536
+ borderRightColor: "transparent",
537
+ borderTopColor: "transparent",
538
+ borderBottomColor: "transparent",
539
+ marginLeft: 3,
540
+ }}
541
+ />
542
+ )}
543
+ </View>
544
+ );
545
+ }
546
+
547
+ case "drag": {
548
+ return (
549
+ <View
550
+ style={{
551
+ width: 20,
552
+ height: 12,
553
+ justifyContent: "space-between",
554
+ }}
555
+ >
556
+ <View
557
+ style={{
558
+ height: 2,
559
+ backgroundColor: "rgba(255, 255, 255, 0.7)",
560
+ borderRadius: 1,
561
+ }}
562
+ />
563
+ <View
564
+ style={{
565
+ height: 2,
566
+ backgroundColor: "rgba(255, 255, 255, 0.7)",
567
+ borderRadius: 1,
568
+ }}
569
+ />
570
+ <View
571
+ style={{
572
+ height: 2,
573
+ backgroundColor: "rgba(255, 255, 255, 0.7)",
574
+ borderRadius: 1,
575
+ }}
576
+ />
577
+ </View>
578
+ );
579
+ }
580
+
581
+ case "remove": {
582
+ return (
583
+ <View
584
+ style={{
585
+ width: 24,
586
+ height: 24,
587
+ borderRadius: 12,
588
+ borderWidth: 1.5,
589
+ borderColor: "rgba(255, 255, 255, 0.7)",
590
+ justifyContent: "center",
591
+ alignItems: "center",
592
+ }}
593
+ >
594
+ <View
595
+ style={{
596
+ width: 10,
597
+ height: 1.5,
598
+ backgroundColor: "rgba(255, 255, 255, 0.7)",
599
+ borderRadius: 1,
600
+ }}
601
+ />
602
+ </View>
603
+ );
604
+ }
605
+
606
+ case "multiSelect": {
607
+ if (!isSelected) {
608
+ return (
609
+ <View
610
+ style={{
611
+ width: layout.assetWidth,
612
+ height: layout.assetHeight,
613
+ borderRadius: layout.assetWidth / 2,
614
+ borderWidth: 1.5,
615
+ borderColor: "rgba(255, 255, 255, 0.7)",
616
+ backgroundColor: "transparent",
617
+ }}
618
+ />
619
+ );
620
+ }
621
+
622
+ return (
623
+ <View
624
+ style={{
625
+ width: 24,
626
+ height: 24,
627
+ borderRadius: 12,
628
+ backgroundColor: "#FFFFFF",
629
+ justifyContent: "center",
630
+ alignItems: "center",
631
+ }}
632
+ >
633
+ <View
634
+ style={{
635
+ width: 6,
636
+ height: 10,
637
+ borderBottomWidth: 2,
638
+ borderRightWidth: 2,
639
+ borderColor: "#1e1e1e",
640
+ transform: [{ rotate: "45deg" }],
641
+ marginTop: -2,
642
+ }}
643
+ />
644
+ </View>
645
+ );
646
+ }
647
+
648
+ default:
649
+ return null;
650
+ }
651
+ };
652
+
494
653
  return (
495
- <View
496
- style={{
497
- paddingTop: layout.paddingTop,
498
- paddingRight: layout.paddingRight,
499
- paddingBottom: layout.paddingBottom,
500
- paddingLeft: layout.paddingLeft,
501
- opacity: focused ? 0.7 : 1,
654
+ <Pressable
655
+ onPress={(e: any) => {
656
+ e?.stopPropagation?.();
657
+ onPress?.();
502
658
  }}
659
+ style={({ pressed }) => [
660
+ {
661
+ paddingTop: layout.paddingTop,
662
+ paddingRight: layout.paddingRight,
663
+ paddingBottom: layout.paddingBottom,
664
+ paddingLeft: layout.paddingLeft,
665
+ opacity: pressed || focused ? 0.7 : 1,
666
+ },
667
+ ]}
503
668
  >
504
- <Image
505
- source={{ uri: BUTTON_ASSETS[action] }}
506
- style={{ width: layout.assetWidth, height: layout.assetHeight }}
507
- resizeMode="contain"
508
- />
509
- </View>
669
+ {renderIcon()}
670
+ </Pressable>
510
671
  );
511
672
  }
512
673
 
@@ -515,6 +676,10 @@ type ItemButtonsProps = {
515
676
  trailingButton?: ItemButtonAction;
516
677
  focusedLeadingButton?: boolean;
517
678
  focusedTrailingButton?: boolean;
679
+ onLeadingButtonPress?: () => void;
680
+ onTrailingButtonPress?: () => void;
681
+ isSelected?: boolean;
682
+ renderHandle?: (element: React.ReactElement) => React.ReactElement;
518
683
  };
519
684
 
520
685
  function ItemButtons({
@@ -522,6 +687,10 @@ function ItemButtons({
522
687
  trailingButton,
523
688
  focusedLeadingButton = false,
524
689
  focusedTrailingButton = false,
690
+ onLeadingButtonPress,
691
+ onTrailingButtonPress,
692
+ isSelected = false,
693
+ renderHandle,
525
694
  }: ItemButtonsProps) {
526
695
  if (!leadingButton && !trailingButton) {
527
696
  return null;
@@ -531,28 +700,62 @@ function ItemButtons({
531
700
  <View style={styles.buttonsRow}>
532
701
  {leadingButton ? (
533
702
  <>
534
- <ItemButton action={leadingButton} focused={focusedLeadingButton} />
703
+ <ItemButton
704
+ action={leadingButton}
705
+ focused={focusedLeadingButton}
706
+ onPress={onLeadingButtonPress}
707
+ isSelected={isSelected}
708
+ />
535
709
  <View style={{ width: BUTTONS_CONTAINER_HORIZONTAL_GUTTER }} />
536
710
  </>
537
711
  ) : null}
538
712
  {trailingButton ? (
539
- <ItemButton action={trailingButton} focused={focusedTrailingButton} />
713
+ trailingButton === "drag" ? (
714
+ renderHandle ? (
715
+ renderHandle(
716
+ <ItemButton
717
+ action={trailingButton}
718
+ focused={focusedTrailingButton}
719
+ onPress={onTrailingButtonPress}
720
+ isSelected={isSelected}
721
+ />
722
+ )
723
+ ) : (
724
+ <ItemButton
725
+ action={trailingButton}
726
+ focused={focusedTrailingButton}
727
+ onPress={onTrailingButtonPress}
728
+ isSelected={isSelected}
729
+ />
730
+ )
731
+ ) : (
732
+ <ItemButton
733
+ action={trailingButton}
734
+ focused={focusedTrailingButton}
735
+ onPress={onTrailingButtonPress}
736
+ isSelected={isSelected}
737
+ />
738
+ )
540
739
  ) : null}
541
740
  </View>
542
741
  );
543
742
  }
544
743
 
545
744
  // ---------------------------------------------------------------------------
546
- // Item
745
+ // AudioPlayerTrackItem
547
746
  // ---------------------------------------------------------------------------
548
747
 
549
- export function Item({
748
+ export function AudioPlayerTrackItem({
550
749
  configuration,
551
750
  data,
552
751
  focused = false,
553
752
  focusedLeadingButton = false,
554
753
  focusedTrailingButton = false,
555
- }: ItemProps) {
754
+ onPress,
755
+ onLeadingButtonPress,
756
+ onTrailingButtonPress,
757
+ renderHandle,
758
+ }: AudioPlayerTrackItemProps) {
556
759
  const {
557
760
  background,
558
761
  image,
@@ -566,7 +769,12 @@ export function Item({
566
769
  const showNowPlaying = !!data.nowPlayingLabel;
567
770
 
568
771
  return (
569
- <ItemContainer image={image} background={background} focused={focused}>
772
+ <ItemContainer
773
+ image={image}
774
+ background={background}
775
+ focused={focused}
776
+ onPress={onPress}
777
+ >
570
778
  <View style={styles.row}>
571
779
  <ItemThumbnail configuration={image} imageUri={data.imageUri} />
572
780
  <View style={{ width: container.horizontalGutter }} />
@@ -594,6 +802,10 @@ export function Item({
594
802
  trailingButton={data.trailingButton}
595
803
  focusedLeadingButton={focusedLeadingButton}
596
804
  focusedTrailingButton={focusedTrailingButton}
805
+ onLeadingButtonPress={onLeadingButtonPress}
806
+ onTrailingButtonPress={onTrailingButtonPress}
807
+ isSelected={data.isSelected}
808
+ renderHandle={renderHandle}
597
809
  />
598
810
  </View>
599
811
  </ItemContainer>
@@ -663,47 +875,3 @@ export const VARIANT_PLAYLIST_ITEM: ItemConfiguration = {
663
875
  textLabel1: DEFAULT_TEXT_LABEL_1_CONFIGURATION,
664
876
  textLabel2: DEFAULT_TEXT_LABEL_2_CONFIGURATION,
665
877
  };
666
-
667
- export default function App() {
668
- // Hardcoded core mock data objects matching user types framework
669
- const sharedMockItem: MenuItem = {
670
- title: "Content Title",
671
- summary: "Artist",
672
- icon: "https://picsum.photos/200",
673
- action: { type: "PLAY_AUDIO" },
674
- secondaryAction: { action: { type: "OPEN_CONTEXT_MENU" } },
675
- };
676
-
677
- const playlistMockItem: MenuItem = {
678
- title: "Playlist Name",
679
- summary: "12 Songs",
680
- icon: "https://picsum.photos/201",
681
- isSelected: true,
682
- action: { type: "NAVIGATE_PLAYLIST" },
683
- };
684
-
685
- return (
686
- <View style={styles.demoScreen}>
687
- <Text style={styles.demoHeading}>Modal Item Building Block</Text>
688
-
689
- <Text style={styles.demoLabel}>Now Playing</Text>
690
- <Item
691
- configuration={VARIANT_NOW_PLAYING}
692
- data={mapMenuItemToNowPlayingData(sharedMockItem)}
693
- />
694
-
695
- <Text style={styles.demoLabel}>Queued Item</Text>
696
- <Item
697
- configuration={VARIANT_QUEUED_ITEM}
698
- data={mapMenuItemToQueuedData(sharedMockItem)}
699
- focused
700
- />
701
-
702
- <Text style={styles.demoLabel}>Playlist Item</Text>
703
- <Item
704
- configuration={VARIANT_PLAYLIST_ITEM}
705
- data={mapMenuItemToPlaylistData(playlistMockItem)}
706
- />
707
- </View>
708
- );
709
- }
@@ -0,0 +1,86 @@
1
+ import React from "react";
2
+ import { StyleSheet, View } from "react-native";
3
+ import {
4
+ AudioPlayerTrackItem,
5
+ VARIANT_NOW_PLAYING,
6
+ mapMenuItemToNowPlayingData,
7
+ } from "./Item";
8
+ import { AudioPlayerHeader, VARIANT_SECTION_HEADER } from "./Header";
9
+ import type { MenuItem } from "@applicaster/zapp-react-native-utils/modalState/types";
10
+ import { usePlayer } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer";
11
+ import { usePlayerState } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayerState";
12
+
13
+ const styles = StyleSheet.create({
14
+ divider: {
15
+ height: StyleSheet.hairlineWidth,
16
+ backgroundColor: "rgba(255, 255, 255, 0.1)",
17
+ marginHorizontal: 20,
18
+ marginVertical: 8,
19
+ },
20
+ });
21
+
22
+ type NowPlayingHeaderSectionProps = {
23
+ onClear?: () => void;
24
+ };
25
+
26
+ export function NowPlayingHeaderSection({
27
+ onClear,
28
+ }: NowPlayingHeaderSectionProps) {
29
+ const player = usePlayer();
30
+ const playerState = usePlayerState("queue-now-playing-section");
31
+
32
+ const renderNowPlayingTrack = () => {
33
+ if (!player?.entry) {
34
+ return null;
35
+ }
36
+
37
+ const mediaGroup = player.entry.media_group;
38
+
39
+ const imageGroup = Array.isArray(mediaGroup)
40
+ ? mediaGroup.find((g: any) => g.type === "image")
41
+ : null;
42
+
43
+ const firstImage = Array.isArray(imageGroup?.media_item)
44
+ ? imageGroup.media_item[0]?.src
45
+ : imageGroup?.media_item?.src;
46
+
47
+ const nowPlayingItem: MenuItem = {
48
+ title: (player.entry.title as string) || String(player.entry.id),
49
+ summary: (player.entry.summary as string) ?? "",
50
+ icon: firstImage,
51
+ };
52
+
53
+ const data = mapMenuItemToNowPlayingData(nowPlayingItem);
54
+ data.isSelected = !playerState?.isPaused;
55
+
56
+ const handlePlayPause = () => {
57
+ if (playerState?.isPaused) {
58
+ player?.play();
59
+ } else {
60
+ player?.pause();
61
+ }
62
+ };
63
+
64
+ return (
65
+ <View>
66
+ <AudioPlayerTrackItem
67
+ configuration={VARIANT_NOW_PLAYING}
68
+ data={data}
69
+ onTrailingButtonPress={handlePlayPause}
70
+ />
71
+ <View style={styles.divider} />
72
+ </View>
73
+ );
74
+ };
75
+
76
+ return (
77
+ <View>
78
+ {renderNowPlayingTrack()}
79
+ <AudioPlayerHeader
80
+ configuration={VARIANT_SECTION_HEADER}
81
+ data={{ title: "Next Up" }}
82
+ onClear={onClear}
83
+ />
84
+ </View>
85
+ );
86
+ }
@@ -1,7 +1,11 @@
1
- export { Item as AudioPlayerItem } from "./Item";
1
+ export { AudioPlayerAction } from "./Action";
2
2
 
3
- export { Item as AudioPlayerHeader } from "./Header";
3
+ export { AudioPlayerButton, VARIANT_WITH_ICON } from "./Button";
4
4
 
5
- export { Item as AudioPlayerButton } from "./Button";
5
+ export { AudioPlayerHeader } from "./Header";
6
6
 
7
- export { Item as AudioPlayerAction } from "./Action";
7
+ export { AudioPlayerTrackItem } from "./Item";
8
+
9
+ export { NowPlayingHeaderSection } from "./NowPlayingHeaderSection";
10
+
11
+ export { AudioPlayerInput } from "./Input";