feat: individual shuffle and repeat/loop button of player

This commit is contained in:
Kingkor Roy Tirtho 2023-01-06 12:10:03 +06:00
parent e7f3f4eae4
commit f79223cd41
9 changed files with 95 additions and 83 deletions

View File

@ -39,11 +39,12 @@ class UserAlbums extends HookConsumerWidget {
final searchText = useState(''); final searchText = useState('');
final albums = useMemoized(() { final albums = useMemoized(() {
if (searchText.value.isEmpty) {
return albumsQuery.data?.toList() ?? [];
}
return albumsQuery.data return albumsQuery.data
?.map((e) => Tuple2( ?.map((e) => Tuple2(
searchText.value.isEmpty weightedRatio(e.name!, searchText.value),
? 100
: weightedRatio(e.name!, searchText.value),
e, e,
)) ))
.sorted((a, b) => b.item1.compareTo(a.item1)) .sorted((a, b) => b.item1.compareTo(a.item1))

View File

@ -33,12 +33,15 @@ class UserArtists extends HookConsumerWidget {
final searchText = useState(''); final searchText = useState('');
final filteredArtists = useMemoized(() { final filteredArtists = useMemoized(() {
return artistQuery.pages final artists = artistQuery.pages
.expand<Artist>((page) => page?.items ?? const Iterable.empty()) .expand<Artist>((page) => page?.items ?? const Iterable.empty());
if (searchText.value.isEmpty) {
return artists.toList();
}
return artists
.map((e) => Tuple2( .map((e) => Tuple2(
searchText.value.isEmpty weightedRatio(e.name!, searchText.value),
? 100
: weightedRatio(e.name!, searchText.value),
e, e,
)) ))
.sorted((a, b) => b.item1.compareTo(a.item1)) .sorted((a, b) => b.item1.compareTo(a.item1))

View File

@ -250,11 +250,12 @@ class UserLocalTracks extends HookConsumerWidget {
}, [sortBy.value, tracks]); }, [sortBy.value, tracks]);
final filteredTracks = useMemoized(() { final filteredTracks = useMemoized(() {
if (searchText.value.isEmpty) {
return sortedTracks;
}
return sortedTracks return sortedTracks
.map((e) => Tuple2( .map((e) => Tuple2(
searchText.value.isEmpty weightedRatio(
? 100
: weightedRatio(
"${e.name} - ${TypeConversionUtils.artists_X_String<Artist>(e.artists ?? [])}", "${e.name} - ${TypeConversionUtils.artists_X_String<Artist>(e.artists ?? [])}",
searchText.value, searchText.value,
), ),

View File

@ -52,20 +52,26 @@ class UserPlaylists extends HookConsumerWidget {
likedTracksPlaylist.images = [image]; likedTracksPlaylist.images = [image];
final playlists = useMemoized( final playlists = useMemoized(
() => [ () {
if (searchText.value.isEmpty) {
return [
likedTracksPlaylist,
...?playlistsQuery.data,
];
}
return [
likedTracksPlaylist, likedTracksPlaylist,
...?playlistsQuery.data, ...?playlistsQuery.data,
] ]
.map((e) => Tuple2( .map((e) => Tuple2(
searchText.value.isEmpty weightedRatio(e.name!, searchText.value),
? 100
: weightedRatio(e.name!, searchText.value),
e, e,
)) ))
.sorted((a, b) => b.item1.compareTo(a.item1)) .sorted((a, b) => b.item1.compareTo(a.item1))
.where((e) => e.item1 > 50) .where((e) => e.item1 > 50)
.map((e) => e.item2) .map((e) => e.item2)
.toList(), .toList();
},
[playlistsQuery.data, searchText.value], [playlistsQuery.data, searchText.value],
); );

View File

@ -139,22 +139,20 @@ class PlayerControls extends HookConsumerWidget {
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
PlatformIconButton( PlatformIconButton(
tooltip: playback.isLoop tooltip: playback.isShuffled
? "Repeat playlist" ? "Unshuffle playlist"
: playback.isShuffled
? "Loop track"
: "Shuffle playlist", : "Shuffle playlist",
icon: Icon( icon: Icon(
playback.isLoop Icons.shuffle_rounded,
? Icons.repeat_one_rounded color: playback.isShuffled
: playback.isShuffled ? PlatformTheme.of(context).primaryColor
? Icons.shuffle_rounded : null,
: Icons.repeat_rounded,
), ),
onPressed: onPressed: playback.playlist == null
playback.track == null || playback.playlist == null
? null ? null
: playback.cyclePlaybackMode, : () {
playback.setIsShuffled(!playback.isShuffled);
},
), ),
PlatformIconButton( PlatformIconButton(
tooltip: "Previous track", tooltip: "Previous track",
@ -209,7 +207,22 @@ class PlayerControls extends HookConsumerWidget {
} }
} }
: null, : null,
) ),
PlatformIconButton(
tooltip:
!playback.isLoop ? "Loop Track" : "Repeat playlist",
icon: Icon(
playback.isLoop
? Icons.repeat_one_rounded
: Icons.repeat_rounded,
),
onPressed:
playback.track == null || playback.playlist == null
? null
: () {
playback.setIsLoop(!playback.isLoop);
},
),
], ],
), ),
const SizedBox(height: 5) const SizedBox(height: 5)

View File

@ -103,11 +103,12 @@ class TrackCollectionView<T> extends HookConsumerWidget {
final searchText = useState(""); final searchText = useState("");
final filteredTracks = useMemoized(() { final filteredTracks = useMemoized(() {
if (searchText.value.isEmpty) {
return tracksSnapshot.data;
}
return tracksSnapshot.data return tracksSnapshot.data
?.map((e) => Tuple2( ?.map((e) => Tuple2(
searchText.value.isEmpty weightedRatio(
? 100
: weightedRatio(
"${e.name} - ${TypeConversionUtils.artists_X_String<Artist>(e.artists ?? [])}", "${e.name} - ${TypeConversionUtils.artists_X_String<Artist>(e.artists ?? [])}",
searchText.value, searchText.value,
), ),

View File

@ -40,7 +40,7 @@ class CurrentPlaylist {
// won't shuffle if already shuffled // won't shuffle if already shuffled
if (_tempTrack == null) { if (_tempTrack == null) {
_tempTrack = [...tracks]; _tempTrack = [...tracks];
tracks.shuffle(); tracks = List.from(tracks)..shuffle();
if (topTrack != null) { if (topTrack != null) {
tracks.remove(topTrack); tracks.remove(topTrack);
tracks.insert(0, topTrack); tracks.insert(0, topTrack);

View File

@ -39,15 +39,10 @@ enum AudioQuality {
low, low,
} }
enum PlaybackMode {
repeat,
shuffle,
normal,
}
class Playback extends PersistedChangeNotifier { class Playback extends PersistedChangeNotifier {
// player properties // player properties
PlaybackMode playbackMode; bool isShuffled;
bool isLoop;
bool isPlaying; bool isPlaying;
Duration currentDuration; Duration currentDuration;
double volume; double volume;
@ -83,7 +78,8 @@ class Playback extends PersistedChangeNotifier {
this.mobileAudioService, this.mobileAudioService,
}) : volume = 1, }) : volume = 1,
isPlaying = false, isPlaying = false,
playbackMode = PlaybackMode.normal, isShuffled = false,
isLoop = false,
currentDuration = Duration.zero, currentDuration = Duration.zero,
_subscriptions = [], _subscriptions = [],
status = PlaybackStatus.idle, status = PlaybackStatus.idle,
@ -287,25 +283,18 @@ class Playback extends PersistedChangeNotifier {
isPlaying ? await pause() : await resume(); isPlaying ? await pause() : await resume();
} }
void cyclePlaybackMode() { void setIsShuffled(bool shuffle) {
switch (playbackMode) { isShuffled = shuffle;
case PlaybackMode.normal: if (isShuffled) {
playbackMode = PlaybackMode.shuffle;
playlist?.shuffle(track); playlist?.shuffle(track);
break; } else {
case PlaybackMode.shuffle:
playbackMode = PlaybackMode.repeat;
playlist?.unshuffle(); playlist?.unshuffle();
break;
case PlaybackMode.repeat:
playbackMode = PlaybackMode.normal;
break;
} }
notifyListeners(); notifyListeners();
} }
void setPlaybackMode(PlaybackMode mode) { void setIsLoop(bool loop) {
playbackMode = mode; isLoop = loop;
notifyListeners(); notifyListeners();
} }
@ -325,7 +314,8 @@ class Playback extends PersistedChangeNotifier {
await player.stop(); await player.stop();
await player.release(); await player.release();
isPlaying = false; isPlaying = false;
playbackMode = PlaybackMode.normal; isShuffled = false;
isLoop = false;
playlist = null; playlist = null;
track = null; track = null;
status = PlaybackStatus.idle; status = PlaybackStatus.idle;
@ -685,9 +675,6 @@ class Playback extends PersistedChangeNotifier {
}; };
} }
bool get isLoop => playbackMode == PlaybackMode.repeat;
bool get isShuffled => playbackMode == PlaybackMode.shuffle;
bool get isNormal => playbackMode == PlaybackMode.normal;
UnmodifiableListView<Video> get siblingYtVideos => UnmodifiableListView<Video> get siblingYtVideos =>
UnmodifiableListView(_siblingYtVideos); UnmodifiableListView(_siblingYtVideos);
} }

View File

@ -249,13 +249,15 @@ class _MprisMediaPlayer2Player extends DBusObject {
/// Gets value of property org.mpris.MediaPlayer2.Player.LoopStatus /// Gets value of property org.mpris.MediaPlayer2.Player.LoopStatus
Future<DBusMethodResponse> getLoopStatus() async { Future<DBusMethodResponse> getLoopStatus() async {
return DBusMethodSuccessResponse([const DBusString("Playlist")]); return DBusMethodSuccessResponse([
playback.isLoop ? const DBusString("Track") : const DBusString("None"),
]);
} }
/// Sets property org.mpris.MediaPlayer2.Player.LoopStatus /// Sets property org.mpris.MediaPlayer2.Player.LoopStatus
Future<DBusMethodResponse> setLoopStatus(String value) async { Future<DBusMethodResponse> setLoopStatus(String value) async {
return DBusMethodErrorResponse.failed( playback.setIsLoop(value == "Track");
'Set org.mpris.MediaPlayer2.Player.LoopStatus not implemented'); return DBusMethodSuccessResponse();
} }
/// Gets value of property org.mpris.MediaPlayer2.Player.Rate /// Gets value of property org.mpris.MediaPlayer2.Player.Rate
@ -275,9 +277,7 @@ class _MprisMediaPlayer2Player extends DBusObject {
/// Sets property org.mpris.MediaPlayer2.Player.Shuffle /// Sets property org.mpris.MediaPlayer2.Player.Shuffle
Future<DBusMethodResponse> setShuffle(bool value) async { Future<DBusMethodResponse> setShuffle(bool value) async {
playback.setPlaybackMode( playback.setIsShuffled(value);
value ? PlaybackMode.shuffle : PlaybackMode.normal,
);
return DBusMethodSuccessResponse(); return DBusMethodSuccessResponse();
} }