Skip to content

Commit

Permalink
feat: Allow sorting the player queue
Browse files Browse the repository at this point in the history
closes #27
  • Loading branch information
khaled-0 committed Dec 28, 2024
1 parent 70ea3ad commit e8d070f
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 14 deletions.
24 changes: 17 additions & 7 deletions lib/app/more/preferences/components/choice_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,23 @@ class ChoiceDialog<T> extends StatelessWidget {
child: ListView.builder(
shrinkWrap: true,
itemCount: keys.length,
itemBuilder: (context, i) => RadioListTile(
dense: true,
value: options[keys[i]],
groupValue: selected,
onChanged: (v) => Navigator.pop(context, v),
title: Text(keys[i]),
),
itemBuilder: (context, i) {
final value = options[keys[i]];
if (selected == null) {
return ListTile(
dense: true,
onTap: () => Navigator.pop(context, value),
title: Text(keys[i]),
);
}
return RadioListTile(
dense: true,
value: value,
groupValue: selected,
onChanged: (v) => Navigator.pop(context, v),
title: Text(keys[i]),
);
},
),
),
actions: [
Expand Down
2 changes: 0 additions & 2 deletions lib/app/player/components/player_state_indicator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import 'package:syncara/app/player/player_menu_sheet.dart';
import 'package:syncara/extensions.dart';
import 'package:syncara/provider/player_provider.dart';

// https://github.com/material-components/material-components-android/blob/master/docs/components/Button.md#connected-button-group
// TODO There's no flutter component. Push this upstream someday
class PlayerStateIndicator extends StatelessWidget {
const PlayerStateIndicator({super.key}) : _static = false;

Expand Down
24 changes: 24 additions & 0 deletions lib/app/player/player_queue_sheet.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';
import 'package:provider/provider.dart';
import 'package:syncara/app/more/preferences/components/choice_dialog.dart';
import 'package:syncara/app/player/components/queue_playlist_filter.dart';
import 'package:syncara/app/playlist/media_entry_builder.dart';
import 'package:syncara/model/common.dart';
import 'package:syncara/model/media.dart';
import 'package:syncara/provider/player_provider.dart';

Expand Down Expand Up @@ -95,6 +97,28 @@ class PlayerQueueSheet extends StatelessWidget {
),
),
),
IconButton(
onPressed: () => sortQueueSelector(context),
icon: const Icon(Icons.sort_rounded),
),
];
}

Future<bool> sortQueueSelector(BuildContext context) async {
final result = await showDialog<SortOption?>(
context: context,
builder: (_) => ChoiceDialog<SortOption>(
title: "Sort Current Queue",
icon: const Icon(Icons.speed_rounded),
options: {
for (final option in SortOption.values) ...{option.name: option}
},
),
);

if (!context.mounted || result == null) return false;

context.read<PlayerProvider>().sortQueue(result);
return true;
}
}
3 changes: 3 additions & 0 deletions lib/model/common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,6 @@ class LastPlayedMedia {
@override
int get hashCode => playlistId.hashCode ^ mediaId.hashCode;
}

//ignore: constant_identifier_names
enum SortOption { Ascending, Descending, Reverse, Author, Reset }
43 changes: 38 additions & 5 deletions lib/provider/player_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class PlayerProvider extends ChangeNotifier {

final List<Playlist> _playlistInfo = List.empty(growable: true);
final List<Media> _playlist = List.empty(growable: true);
final List<String> _originalPlaylistOrderIds = List.empty(growable: true);

List<Playlist> get playlistInfo => List.of(_playlistInfo);

Expand Down Expand Up @@ -60,6 +61,7 @@ class PlayerProvider extends ChangeNotifier {
}) {
_playlistInfo.add(provider.playlist);
_playlist.addAll(provider.medias);
_originalPlaylistOrderIds.addAll(_playlist.map((e) => e.id));
prepare?.call(this);
nowPlaying = ValueNotifier(start ?? _playlist.first);
nowPlaying.addListener(beginPlay);
Expand Down Expand Up @@ -107,16 +109,18 @@ class PlayerProvider extends ChangeNotifier {
if (_playlistInfo.contains(provider.playlist)) return;

_playlistInfo.add(provider.playlist);
_playlist.addAll(provider.medias.where(
final uniqueMedias = provider.medias.where(
(media) => !_playlist.contains(media),
));
);
_playlist.addAll(uniqueMedias);
_originalPlaylistOrderIds.addAll(uniqueMedias.map((e) => e.id));
notifyListeners();
}

Future<void> beginPlay() async {
final media = nowPlaying.value;
try {
// HACK: Quickly toggle _disposed flag so stop event doesn't get emitted by notificationState
// HACK: Quickly toggle _disposed flag so stop event doesn't get emitted by notificationState causing spam
_buffering = true;
_disposed = true;
await player.stop();
Expand Down Expand Up @@ -236,12 +240,12 @@ class PlayerProvider extends ChangeNotifier {

void jumpTo(int index) => nowPlaying.value = _playlist[index];

void reorderList(int oldIndex, int newIndex) {
void reorderList(int oldIndex, int newIndex, {bool notify = true}) {
if (oldIndex < newIndex) newIndex -= 1;

final item = _playlist.removeAt(oldIndex);
_playlist.insert(newIndex, item);
notifyListeners();
if (notify) notifyListeners();
}

/// preserveCurrentIndex: Put currently playing song at first
Expand All @@ -264,6 +268,35 @@ class PlayerProvider extends ChangeNotifier {
player.setSpeed(speed);
}

void sortQueue(SortOption option) {
switch (option) {
case SortOption.Ascending:
_playlist.sort((a, b) => a.title.compareTo(b.title));
break;
case SortOption.Descending:
_playlist.sort((a, b) => b.title.compareTo(a.title));
break;
case SortOption.Reverse:
final reversed = _playlist.reversed.toList();
_playlist.clear();
_playlist.addAll(reversed);
break;

case SortOption.Author:
_playlist.sort((a, b) => a.author.compareTo(b.author));
break;

case SortOption.Reset:
for (final (index, id) in _originalPlaylistOrderIds.indexed) {
final old = _playlist.indexWhere((element) => element.id == id);
reorderList(old, index, notify: false);
}
break;
}

notifyListeners();
}

/// Passing null duration & afterSong will cancel the timer
void setSleepTimer({Duration? duration, bool? afterSong}) {
if (duration == null && afterSong == null) {
Expand Down

0 comments on commit e8d070f

Please sign in to comment.