diff --git a/lib/components/AlbumScreen/song_list_tile.dart b/lib/components/AlbumScreen/song_list_tile.dart index aa50b57e..1b680383 100644 --- a/lib/components/AlbumScreen/song_list_tile.dart +++ b/lib/components/AlbumScreen/song_list_tile.dart @@ -387,6 +387,163 @@ class _SongListTileState extends ConsumerState } } + +class QueueListTile extends ConsumerStatefulWidget { + final jellyfin_models.BaseItemDto item; + final jellyfin_models.BaseItemDto? parentItem; + final Future? listIndex; + final int actualIndex; + final int indexOffset; + final bool isCurrentTrack; + final bool isInPlaylist; + final bool allowReorder; + + final void Function() onTap; + final VoidCallback? onRemoveFromList; + final void Function(FinampTheme)? themeCallback; + + const QueueListTile({ + super.key, + required this.item, + required this.listIndex, + required this.actualIndex, + required this.indexOffset, + required this.onTap, + required this.isCurrentTrack, + required this.isInPlaylist, + required this.allowReorder, + this.parentItem, + this.onRemoveFromList, + this.themeCallback, + }); + + @override + ConsumerState createState() => _QueueListTileState(); +} + +class _QueueListTileState extends ConsumerState + with SingleTickerProviderStateMixin { + final _audioServiceHelper = GetIt.instance(); + final _queueService = GetIt.instance(); + final _audioHandler = GetIt.instance(); + + FinampTheme? _menuTheme; + + @override + void dispose() { + _menuTheme?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + bool playable; + if (FinampSettingsHelper.finampSettings.isOffline) { + playable = ref.watch(GetIt.instance() + .stateProvider(DownloadStub.fromItem( + type: DownloadItemType.song, item: widget.item)) + .select((value) => value.value?.isComplete ?? false)); + } else { + playable = true; + } + + final listTile = StreamBuilder( + stream: _audioHandler.mediaItem, + builder: (context, snapshot) { + // I think past me did this check directly from the JSON for + // performance. It works for now, apologies if you're debugging it + // years in the future. + final isCurrentlyPlaying = + snapshot.data?.extras?["itemJson"]["Id"] == widget.item.id; + + final trackListItem = TrackListItem( + item: widget.item, + parentItem: widget.parentItem, + listIndex: widget.listIndex, + actualIndex: widget.item.indexNumber ?? -1, + isCurrentTrack: isCurrentlyPlaying, + isPlayable: playable, + isInPlaylist: widget.isInPlaylist, + allowReorder: widget.allowReorder, + onRemoveFromList: widget.onRemoveFromList, + themeCallback: (x) => _menuTheme = x, + // This must be in ListTile instead of parent GestureDetecter to + // enable hover color changes + onTap: widget.onTap, + ); + + return isCurrentlyPlaying + ? ProviderScope( + overrides: [ + themeDataProvider.overrideWith((ref) { + return ref.watch(playerScreenThemeDataProvider) ?? + FinampTheme.defaultTheme(); + }) + ], + child: Consumer( + builder: + (BuildContext context, WidgetRef ref, Widget? child) { + final imageTheme = ref.watch(playerScreenThemeProvider); + return AnimatedTheme( + duration: const Duration(milliseconds: 500), + data: ThemeData( + colorScheme: imageTheme, + brightness: Theme.of(context).brightness, + iconTheme: Theme.of(context).iconTheme.copyWith( + color: imageTheme.primary, + ), + ), + child: trackListItem, + ); + }, + ), + ) + : trackListItem; + }); + void menuCallback() async { + if (playable) { + FeedbackHelper.feedback(FeedbackType.selection); + await showModalSongMenu( + context: context, + item: widget.item, + isInPlaylist: widget.isInPlaylist, + parentItem: widget.parentItem, + onRemoveFromList: widget.onRemoveFromList, + themeProvider: _menuTheme, + confirmPlaylistRemoval: false, + ); + } + } + + return GestureDetector( + onTapDown: (_) { + _menuTheme?.calculate(Theme.of(context).brightness); + }, + onLongPressStart: (details) => menuCallback(), + onSecondaryTapDown: (details) => menuCallback(), + child: !playable + ? listTile + : Dismissible( + key: Key(widget.listIndex.toString()), + direction: FinampSettingsHelper.finampSettings.disableGesture + ? DismissDirection.none + : DismissDirection.horizontal, + dismissThresholds: const { + DismissDirection.startToEnd: 0.65, + DismissDirection.endToStart: 0.65 + }, + // no background, dismissing really dismisses here + onDismissed: (direction) async { + FeedbackHelper.feedback(FeedbackType.impact); + await _queueService.removeAtOffset(widget.indexOffset); + setState(() {}); + }, + child: listTile, + ), + ); + } +} + class TrackListItem extends ConsumerWidget { final jellyfin_models.BaseItemDto item; final jellyfin_models.BaseItemDto? parentItem; @@ -394,7 +551,8 @@ class TrackListItem extends ConsumerWidget { final int actualIndex; final bool isCurrentTrack; final bool isInPlaylist; - + final bool allowReorder; + final bool isPlayable; final void Function() onTap; final VoidCallback? onRemoveFromList; @@ -410,6 +568,7 @@ class TrackListItem extends ConsumerWidget { this.isPlayable = true, this.isCurrentTrack = false, this.isInPlaylist = false, + this.allowReorder = false, this.onRemoveFromList, this.themeCallback, }); @@ -446,7 +605,8 @@ class TrackListItem extends ConsumerWidget { Brightness.dark ? 0.35 : 0.3) - : Theme.of(context).colorScheme.surfaceContainer, + // : Theme.of(context).colorScheme.surfaceContainer, + : Colors.transparent, ), textTheme: Theme.of(context).textTheme.copyWith( bodyLarge: Theme.of(context) @@ -463,8 +623,10 @@ class TrackListItem extends ConsumerWidget { )), child: TrackListItemTile( baseItem: baseItem, + index: listIndex, themeCallback: themeCallback, isCurrentTrack: isCurrentTrack, + allowReorder: allowReorder, onTap: onTap), )), ); @@ -479,12 +641,16 @@ class TrackListItemTile extends StatelessWidget { required this.baseItem, required this.themeCallback, required this.isCurrentTrack, + required this.allowReorder, required this.onTap, + this.index, }); final jellyfin_models.BaseItemDto baseItem; final void Function(FinampTheme theme)? themeCallback; final bool isCurrentTrack; + final bool allowReorder; + final Future? index; final void Function() onTap; static const double tileHeight = 60.0; @@ -612,6 +778,26 @@ class TrackListItemTile extends StatelessWidget { horizontal: -4, ), ), + if (allowReorder) + FutureBuilder( + future: index, + builder: (context, snapshot) { + return ReorderableDragStartListener( + index: snapshot.data ?? + 0, // will briefly use 0 as index, but should resolve quickly enough for user not to notice + child: Padding( + padding: const EdgeInsets.only(left: 6.0), + child: Icon( + TablerIcons.grip_horizontal, + color: + Theme.of(context).textTheme.bodyMedium?.color ?? + Colors.white, + size: 28.0, + weight: 1.5, + ), + ), + ); + }), ], ), ), diff --git a/lib/components/PlayerScreen/queue_list.dart b/lib/components/PlayerScreen/queue_list.dart index 8deb199c..aca556be 100644 --- a/lib/components/PlayerScreen/queue_list.dart +++ b/lib/components/PlayerScreen/queue_list.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:audio_service/audio_service.dart'; +import 'package:finamp/components/AlbumScreen/song_list_tile.dart'; import 'package:finamp/components/AlbumScreen/song_menu.dart'; import 'package:finamp/components/Buttons/simple_button.dart'; import 'package:finamp/components/AddToPlaylistScreen/add_to_playlist_button.dart'; @@ -648,13 +649,32 @@ class _QueueTracksListState extends State { final actualIndex = index; final indexOffset = index + _nextUp!.length + 1; - return QueueListItem( + // return QueueListItem( + // key: ValueKey(item.id), + // item: item, + // listIndex: index, + // actualIndex: actualIndex, + // indexOffset: indexOffset, + // subqueue: _queue!, + // allowReorder: + // _queueService.playbackOrder == FinampPlaybackOrder.linear, + // onTap: () async { + // FeedbackHelper.feedback(FeedbackType.selection); + // await _queueService.skipByOffset(indexOffset); + // scrollToKey( + // key: widget.previousTracksHeaderKey, + // duration: const Duration(milliseconds: 500)); + // }, + // isCurrentTrack: false, + // ); + return QueueListTile( key: ValueKey(item.id), - item: item, - listIndex: index, + item: item.baseItem!, + listIndex: Future.value(index), actualIndex: actualIndex, indexOffset: indexOffset, - subqueue: _queue!, + isInPlaylist: queueItemInPlaylist(item), + parentItem: item.source.item, allowReorder: _queueService.playbackOrder == FinampPlaybackOrder.linear, onTap: () async { diff --git a/lib/components/PlayerScreen/queue_list_item.dart b/lib/components/PlayerScreen/queue_list_item.dart index 410a3066..902a3d44 100644 --- a/lib/components/PlayerScreen/queue_list_item.dart +++ b/lib/components/PlayerScreen/queue_list_item.dart @@ -14,6 +14,7 @@ import 'package:get_it/get_it.dart'; import '../../services/theme_provider.dart'; +@Deprecated("Use QueueListItem instead") class QueueListItem extends StatefulWidget { final FinampQueueItem item; final int listIndex; @@ -123,7 +124,6 @@ class _QueueListItemState extends State : jellyfin_models.BaseItemDto.fromJson( widget.item.item.extras?["itemJson"]), borderRadius: BorderRadius.zero, - themeCallback: (x) => _menuTheme = x, ), title: Column( crossAxisAlignment: CrossAxisAlignment.start,