mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
feat: individual shuffle and repeat/loop button of player
This commit is contained in:
parent
e7f3f4eae4
commit
f79223cd41
@ -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))
|
||||||
|
@ -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))
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
|
@ -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],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user