@@ -387,14 +387,172 @@ class _SongListTileState extends ConsumerState<SongListTile>
387
387
}
388
388
}
389
389
390
+
391
+ class QueueListTile extends ConsumerStatefulWidget {
392
+ final jellyfin_models.BaseItemDto item;
393
+ final jellyfin_models.BaseItemDto ? parentItem;
394
+ final Future <int >? listIndex;
395
+ final int actualIndex;
396
+ final int indexOffset;
397
+ final bool isCurrentTrack;
398
+ final bool isInPlaylist;
399
+ final bool allowReorder;
400
+
401
+ final void Function () onTap;
402
+ final VoidCallback ? onRemoveFromList;
403
+ final void Function (FinampTheme )? themeCallback;
404
+
405
+ const QueueListTile ({
406
+ super .key,
407
+ required this .item,
408
+ required this .listIndex,
409
+ required this .actualIndex,
410
+ required this .indexOffset,
411
+ required this .onTap,
412
+ required this .isCurrentTrack,
413
+ required this .isInPlaylist,
414
+ required this .allowReorder,
415
+ this .parentItem,
416
+ this .onRemoveFromList,
417
+ this .themeCallback,
418
+ });
419
+
420
+ @override
421
+ ConsumerState <QueueListTile > createState () => _QueueListTileState ();
422
+ }
423
+
424
+ class _QueueListTileState extends ConsumerState <QueueListTile >
425
+ with SingleTickerProviderStateMixin {
426
+ final _audioServiceHelper = GetIt .instance <AudioServiceHelper >();
427
+ final _queueService = GetIt .instance <QueueService >();
428
+ final _audioHandler = GetIt .instance <MusicPlayerBackgroundTask >();
429
+
430
+ FinampTheme ? _menuTheme;
431
+
432
+ @override
433
+ void dispose () {
434
+ _menuTheme? .dispose ();
435
+ super .dispose ();
436
+ }
437
+
438
+ @override
439
+ Widget build (BuildContext context) {
440
+ bool playable;
441
+ if (FinampSettingsHelper .finampSettings.isOffline) {
442
+ playable = ref.watch (GetIt .instance <DownloadsService >()
443
+ .stateProvider (DownloadStub .fromItem (
444
+ type: DownloadItemType .song, item: widget.item))
445
+ .select ((value) => value.value? .isComplete ?? false ));
446
+ } else {
447
+ playable = true ;
448
+ }
449
+
450
+ final listTile = StreamBuilder <MediaItem ?>(
451
+ stream: _audioHandler.mediaItem,
452
+ builder: (context, snapshot) {
453
+ // I think past me did this check directly from the JSON for
454
+ // performance. It works for now, apologies if you're debugging it
455
+ // years in the future.
456
+ final isCurrentlyPlaying =
457
+ snapshot.data? .extras? ["itemJson" ]["Id" ] == widget.item.id;
458
+
459
+ final trackListItem = TrackListItem (
460
+ item: widget.item,
461
+ parentItem: widget.parentItem,
462
+ listIndex: widget.listIndex,
463
+ actualIndex: widget.item.indexNumber ?? - 1 ,
464
+ isCurrentTrack: isCurrentlyPlaying,
465
+ isPlayable: playable,
466
+ isInPlaylist: widget.isInPlaylist,
467
+ allowReorder: widget.allowReorder,
468
+ onRemoveFromList: widget.onRemoveFromList,
469
+ themeCallback: (x) => _menuTheme = x,
470
+ // This must be in ListTile instead of parent GestureDetecter to
471
+ // enable hover color changes
472
+ onTap: widget.onTap,
473
+ );
474
+
475
+ return isCurrentlyPlaying
476
+ ? ProviderScope (
477
+ overrides: [
478
+ themeDataProvider.overrideWith ((ref) {
479
+ return ref.watch (playerScreenThemeDataProvider) ??
480
+ FinampTheme .defaultTheme ();
481
+ })
482
+ ],
483
+ child: Consumer (
484
+ builder:
485
+ (BuildContext context, WidgetRef ref, Widget ? child) {
486
+ final imageTheme = ref.watch (playerScreenThemeProvider);
487
+ return AnimatedTheme (
488
+ duration: const Duration (milliseconds: 500 ),
489
+ data: ThemeData (
490
+ colorScheme: imageTheme,
491
+ brightness: Theme .of (context).brightness,
492
+ iconTheme: Theme .of (context).iconTheme.copyWith (
493
+ color: imageTheme.primary,
494
+ ),
495
+ ),
496
+ child: trackListItem,
497
+ );
498
+ },
499
+ ),
500
+ )
501
+ : trackListItem;
502
+ });
503
+ void menuCallback () async {
504
+ if (playable) {
505
+ FeedbackHelper .feedback (FeedbackType .selection);
506
+ await showModalSongMenu (
507
+ context: context,
508
+ item: widget.item,
509
+ isInPlaylist: widget.isInPlaylist,
510
+ parentItem: widget.parentItem,
511
+ onRemoveFromList: widget.onRemoveFromList,
512
+ themeProvider: _menuTheme,
513
+ confirmPlaylistRemoval: false ,
514
+ );
515
+ }
516
+ }
517
+
518
+ return GestureDetector (
519
+ onTapDown: (_) {
520
+ _menuTheme? .calculate (Theme .of (context).brightness);
521
+ },
522
+ onLongPressStart: (details) => menuCallback (),
523
+ onSecondaryTapDown: (details) => menuCallback (),
524
+ child: ! playable
525
+ ? listTile
526
+ : Dismissible (
527
+ key: Key (widget.listIndex.toString ()),
528
+ direction: FinampSettingsHelper .finampSettings.disableGesture
529
+ ? DismissDirection .none
530
+ : DismissDirection .horizontal,
531
+ dismissThresholds: const {
532
+ DismissDirection .startToEnd: 0.65 ,
533
+ DismissDirection .endToStart: 0.65
534
+ },
535
+ // no background, dismissing really dismisses here
536
+ onDismissed: (direction) async {
537
+ FeedbackHelper .feedback (FeedbackType .impact);
538
+ await _queueService.removeAtOffset (widget.indexOffset);
539
+ setState (() {});
540
+ },
541
+ child: listTile,
542
+ ),
543
+ );
544
+ }
545
+ }
546
+
390
547
class TrackListItem extends ConsumerWidget {
391
548
final jellyfin_models.BaseItemDto item;
392
549
final jellyfin_models.BaseItemDto ? parentItem;
393
550
final Future <int >? listIndex;
394
551
final int actualIndex;
395
552
final bool isCurrentTrack;
396
553
final bool isInPlaylist;
397
-
554
+ final bool allowReorder;
555
+
398
556
final bool isPlayable;
399
557
final void Function () onTap;
400
558
final VoidCallback ? onRemoveFromList;
@@ -410,6 +568,7 @@ class TrackListItem extends ConsumerWidget {
410
568
this .isPlayable = true ,
411
569
this .isCurrentTrack = false ,
412
570
this .isInPlaylist = false ,
571
+ this .allowReorder = false ,
413
572
this .onRemoveFromList,
414
573
this .themeCallback,
415
574
});
@@ -446,7 +605,8 @@ class TrackListItem extends ConsumerWidget {
446
605
Brightness .dark
447
606
? 0.35
448
607
: 0.3 )
449
- : Theme .of (context).colorScheme.surfaceContainer,
608
+ // : Theme.of(context).colorScheme.surfaceContainer,
609
+ : Colors .transparent,
450
610
),
451
611
textTheme: Theme .of (context).textTheme.copyWith (
452
612
bodyLarge: Theme .of (context)
@@ -463,8 +623,10 @@ class TrackListItem extends ConsumerWidget {
463
623
)),
464
624
child: TrackListItemTile (
465
625
baseItem: baseItem,
626
+ index: listIndex,
466
627
themeCallback: themeCallback,
467
628
isCurrentTrack: isCurrentTrack,
629
+ allowReorder: allowReorder,
468
630
onTap: onTap),
469
631
)),
470
632
);
@@ -479,12 +641,16 @@ class TrackListItemTile extends StatelessWidget {
479
641
required this .baseItem,
480
642
required this .themeCallback,
481
643
required this .isCurrentTrack,
644
+ required this .allowReorder,
482
645
required this .onTap,
646
+ this .index,
483
647
});
484
648
485
649
final jellyfin_models.BaseItemDto baseItem;
486
650
final void Function (FinampTheme theme)? themeCallback;
487
651
final bool isCurrentTrack;
652
+ final bool allowReorder;
653
+ final Future <int >? index;
488
654
final void Function () onTap;
489
655
490
656
static const double tileHeight = 60.0 ;
@@ -612,6 +778,26 @@ class TrackListItemTile extends StatelessWidget {
612
778
horizontal: - 4 ,
613
779
),
614
780
),
781
+ if (allowReorder)
782
+ FutureBuilder (
783
+ future: index,
784
+ builder: (context, snapshot) {
785
+ return ReorderableDragStartListener (
786
+ index: snapshot.data ??
787
+ 0 , // will briefly use 0 as index, but should resolve quickly enough for user not to notice
788
+ child: Padding (
789
+ padding: const EdgeInsets .only (left: 6.0 ),
790
+ child: Icon (
791
+ TablerIcons .grip_horizontal,
792
+ color:
793
+ Theme .of (context).textTheme.bodyMedium? .color ??
794
+ Colors .white,
795
+ size: 28.0 ,
796
+ weight: 1.5 ,
797
+ ),
798
+ ),
799
+ );
800
+ }),
615
801
],
616
802
),
617
803
),
0 commit comments