Skip to content

Commit

Permalink
Merge pull request #481 from ray-kast/main
Browse files Browse the repository at this point in the history
Add Play Next & Add to Queue for songs and albums.
  • Loading branch information
jmshrv authored Aug 19, 2023
2 parents 2675bf9 + 7b0f169 commit ad241ba
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 21 deletions.
36 changes: 28 additions & 8 deletions lib/components/AlbumScreen/song_list_tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import 'downloaded_indicator.dart';

enum SongListTileMenuItems {
addToQueue,
playNext,
replaceQueueWithItem,
addToPlaylist,
removeFromPlaylist,
Expand Down Expand Up @@ -238,13 +239,22 @@ class _SongListTileState extends State<SongListTile> {
screenSize.height - details.globalPosition.dy,
),
items: [
PopupMenuItem<SongListTileMenuItems>(
value: SongListTileMenuItems.addToQueue,
child: ListTile(
leading: const Icon(Icons.queue_music),
title: Text(AppLocalizations.of(context)!.addToQueue),
if (_audioServiceHelper.hasQueueItems()) ...[
PopupMenuItem<SongListTileMenuItems>(
value: SongListTileMenuItems.addToQueue,
child: ListTile(
leading: const Icon(Icons.queue_music),
title: Text(AppLocalizations.of(context)!.addToQueue),
),
),
),
PopupMenuItem<SongListTileMenuItems>(
value: SongListTileMenuItems.playNext,
child: ListTile(
leading: const Icon(Icons.queue_music),
title: Text(AppLocalizations.of(context)!.playNext),
),
),
],
PopupMenuItem<SongListTileMenuItems>(
value: SongListTileMenuItems.replaceQueueWithItem,
child: ListTile(
Expand Down Expand Up @@ -324,7 +334,7 @@ class _SongListTileState extends State<SongListTile> {

switch (selection) {
case SongListTileMenuItems.addToQueue:
await _audioServiceHelper.addQueueItem(widget.item);
await _audioServiceHelper.addQueueItems([widget.item]);

if (!mounted) return;

Expand All @@ -333,6 +343,16 @@ class _SongListTileState extends State<SongListTile> {
));
break;

case SongListTileMenuItems.playNext:
await _audioServiceHelper.insertQueueItemsNext([widget.item]);

if (!mounted) return;

ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(AppLocalizations.of(context)!.insertedIntoQueue),
));
break;

case SongListTileMenuItems.replaceQueueWithItem:
await _audioServiceHelper
.replaceQueueWithItem(itemList: [widget.item]);
Expand Down Expand Up @@ -451,7 +471,7 @@ class _SongListTileState extends State<SongListTile> {
),
),
confirmDismiss: (direction) async {
await _audioServiceHelper.addQueueItem(widget.item);
await _audioServiceHelper.addQueueItems([widget.item]);

if (!mounted) return false;

Expand Down
67 changes: 62 additions & 5 deletions lib/components/MusicScreen/album_item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:get_it/get_it.dart';

import '../../models/jellyfin_models.dart';
import '../../services/audio_service_helper.dart';
import '../../services/jellyfin_api_helper.dart';
import '../../screens/artist_screen.dart';
import '../../screens/album_screen.dart';
import '../error_snackbar.dart';
import 'album_item_card.dart';

enum _AlbumListTileMenuItems {
addToQueue,
playNext,
addFavourite,
removeFavourite,
addToMixList,
Expand Down Expand Up @@ -58,6 +61,8 @@ class AlbumItem extends StatefulWidget {
}

class _AlbumItemState extends State<AlbumItem> {
final _audioServiceHelper = GetIt.instance<AudioServiceHelper>();

late BaseItemDto mutableAlbum;

late Function() onTap;
Expand Down Expand Up @@ -93,11 +98,7 @@ class _AlbumItemState extends State<AlbumItem> {
onLongPressStart: (details) async {
Feedback.forLongPress(context);

if (FinampSettingsHelper.finampSettings.isOffline) {
// If offline, don't show the context menu since the only options here
// are for online.
return;
}
final isOffline = FinampSettingsHelper.finampSettings.isOffline;

final jellyfinApiHelper = GetIt.instance<JellyfinApiHelper>();

Expand All @@ -110,34 +111,58 @@ class _AlbumItemState extends State<AlbumItem> {
screenSize.height - details.globalPosition.dy,
),
items: [
if (_audioServiceHelper.hasQueueItems()) ...[
PopupMenuItem<_AlbumListTileMenuItems>(
value: _AlbumListTileMenuItems.addToQueue,
child: ListTile(
leading: const Icon(Icons.queue_music),
title: Text(AppLocalizations.of(context)!.addToQueue),
),
),
PopupMenuItem<_AlbumListTileMenuItems>(
value: _AlbumListTileMenuItems.playNext,
child: ListTile(
leading: const Icon(Icons.queue_music),
title: Text(AppLocalizations.of(context)!.playNext),
),
),
],
mutableAlbum.userData!.isFavorite
? PopupMenuItem<_AlbumListTileMenuItems>(
enabled: !isOffline,
value: _AlbumListTileMenuItems.removeFavourite,
child: ListTile(
enabled: !isOffline,
leading: const Icon(Icons.favorite_border),
title:
Text(AppLocalizations.of(context)!.removeFavourite),
),
)
: PopupMenuItem<_AlbumListTileMenuItems>(
enabled: !isOffline,
value: _AlbumListTileMenuItems.addFavourite,
child: ListTile(
enabled: !isOffline,
leading: const Icon(Icons.favorite),
title: Text(AppLocalizations.of(context)!.addFavourite),
),
),
jellyfinApiHelper.selectedMixAlbumIds.contains(mutableAlbum.id)
? PopupMenuItem<_AlbumListTileMenuItems>(
enabled: !isOffline,
value: _AlbumListTileMenuItems.removeFromMixList,
child: ListTile(
enabled: !isOffline,
leading: const Icon(Icons.explore_off),
title:
Text(AppLocalizations.of(context)!.removeFromMix),
),
)
: PopupMenuItem<_AlbumListTileMenuItems>(
enabled: !isOffline,
value: _AlbumListTileMenuItems.addToMixList,
child: ListTile(
enabled: !isOffline,
leading: const Icon(Icons.explore),
title: Text(AppLocalizations.of(context)!.addToMix),
),
Expand All @@ -148,6 +173,38 @@ class _AlbumItemState extends State<AlbumItem> {
if (!mounted) return;

switch (selection) {
case _AlbumListTileMenuItems.addToQueue:
final children = await jellyfinApiHelper.getItems(
parentItem: widget.album,
sortBy: "ParentIndexNumber,IndexNumber,SortName",
includeItemTypes: "Audio",
isGenres: false,
);
await _audioServiceHelper.addQueueItems(children!);

if (!mounted) return;

ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(AppLocalizations.of(context)!.addedToQueue),
));
break;

case _AlbumListTileMenuItems.playNext:
final children = await jellyfinApiHelper.getItems(
parentItem: widget.album,
sortBy: "ParentIndexNumber,IndexNumber,SortName",
includeItemTypes: "Audio",
isGenres: false,
);
await _audioServiceHelper.insertQueueItemsNext(children!);

if (!mounted) return;

ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(AppLocalizations.of(context)!.insertedIntoQueue),
));
break;

case _AlbumListTileMenuItems.addFavourite:
try {
final newUserData =
Expand Down
16 changes: 14 additions & 2 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,13 @@
"queue": "Queue",
"@queue": {},
"addToQueue": "Add to Queue",
"@addToQueue": {},
"@addToQueue": {
"description": "Popup menu item title for adding an item to the end of the play queue."
},
"playNext": "Play Next",
"@playNext": {
"description": "Popup menu item title for inserting an item into the play queue after the currently-playing item."
},
"replaceQueue": "Replace Queue",
"@replaceQueue": {},
"instantMix": "Instant Mix",
Expand All @@ -429,7 +435,13 @@
"addFavourite": "Add Favourite",
"@addFavourite": {},
"addedToQueue": "Added to queue.",
"@addedToQueue": {},
"@addedToQueue": {
"description": "Snackbar message that shows when the user successfully adds items to the end of the play queue."
},
"insertedIntoQueue": "Inserted into queue.",
"@insertedIntoQueue": {
"description": "Snackbar message that shows when the user successfully inserts items into the play queue at a location that is not necessarily the end."
},
"queueReplaced": "Queue replaced.",
"@queueReplaced": {},
"removedFromPlaylist": "Removed from playlist.",
Expand Down
33 changes: 30 additions & 3 deletions lib/services/audio_service_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,44 @@ class AudioServiceHelper {
}
}

bool hasQueueItems() {
return (_audioHandler.queue.valueOrNull?.length ?? 0) != 0;
}

@Deprecated("Use addQueueItems instead")
Future<void> addQueueItem(BaseItemDto item) async {
await addQueueItems([item]);
}

Future<void> addQueueItems(List<BaseItemDto> items) async {
try {
// If the queue is empty (like when the app is first launched), run the
// replace queue function instead so that the song gets played
if ((_audioHandler.queue.valueOrNull?.length ?? 0) == 0) {
await replaceQueueWithItem(itemList: [item]);
await replaceQueueWithItem(itemList: items);
return;
}

final mediaItems =
await Future.wait(items.map((i) => _generateMediaItem(i)));
await _audioHandler.addQueueItems(mediaItems);
} catch (e) {
audioServiceHelperLogger.severe(e);
return Future.error(e);
}
}

Future<void> insertQueueItemsNext(List<BaseItemDto> items) async {
try {
// See above comment in addQueueItem
if ((_audioHandler.queue.valueOrNull?.length ?? 0) == 0) {
await replaceQueueWithItem(itemList: items);
return;
}

final itemMediaItem = await _generateMediaItem(item);
await _audioHandler.addQueueItem(itemMediaItem);
final mediaItems =
await Future.wait(items.map((i) => _generateMediaItem(i)));
await _audioHandler.insertQueueItemsNext(mediaItems);
} catch (e) {
audioServiceHelperLogger.severe(e);
return Future.error(e);
Expand Down
Loading

0 comments on commit ad241ba

Please sign in to comment.