fix: re-enable add to queue and play next support, favorite button query exceptions

This commit is contained in:
Kingkor Roy Tirtho 2023-05-27 21:53:56 +06:00
parent bf59570251
commit e529c79c4f
9 changed files with 87 additions and 38 deletions

View File

@ -11,7 +11,6 @@ import 'package:spotube/services/mutations/mutations.dart';
import 'package:spotube/services/queries/queries.dart'; import 'package:spotube/services/queries/queries.dart';
import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart';
import 'package:tuple/tuple.dart';
class HeartButton extends HookConsumerWidget { class HeartButton extends HookConsumerWidget {
final bool isLiked; final bool isLiked;
@ -60,8 +59,11 @@ class HeartButton extends HookConsumerWidget {
} }
} }
Tuple3<bool, Mutation<bool, dynamic, bool>, Query<User, dynamic>> ({
useTrackToggleLike(Track track, WidgetRef ref) { bool isLiked,
Mutation<bool, dynamic, bool> toggleTrackLike,
Query<User?, dynamic> me,
}) useTrackToggleLike(Track track, WidgetRef ref) {
final me = useQueries.user.me(ref); final me = useQueries.user.me(ref);
final savedTracks = final savedTracks =
@ -101,7 +103,7 @@ Tuple3<bool, Mutation<bool, dynamic, bool>, Query<User, dynamic>>
}, },
); );
return Tuple3(isLiked, toggleTrackLike, me); return (isLiked: isLiked, toggleTrackLike: toggleTrackLike, me: me);
} }
class TrackHeartButton extends HookConsumerWidget { class TrackHeartButton extends HookConsumerWidget {
@ -116,18 +118,18 @@ class TrackHeartButton extends HookConsumerWidget {
final savedTracks = final savedTracks =
useQueries.playlist.tracksOfQuery(ref, "user-liked-tracks"); useQueries.playlist.tracksOfQuery(ref, "user-liked-tracks");
final toggler = useTrackToggleLike(track, ref); final toggler = useTrackToggleLike(track, ref);
if (toggler.item3.isLoading || !toggler.item3.hasData) { if (toggler.me.isLoading || !toggler.me.hasData) {
return const CircularProgressIndicator(); return const CircularProgressIndicator();
} }
return HeartButton( return HeartButton(
tooltip: toggler.item1 tooltip: toggler.isLiked
? context.l10n.remove_from_favorites ? context.l10n.remove_from_favorites
: context.l10n.save_as_favorite, : context.l10n.save_as_favorite,
isLiked: toggler.item1, isLiked: toggler.isLiked,
onPressed: savedTracks.hasData onPressed: savedTracks.hasData
? () { ? () {
toggler.item2.mutate(toggler.item1); toggler.toggleTrackLike.mutate(toggler.isLiked);
} }
: null, : null,
); );

View File

@ -316,8 +316,9 @@ class TrackTile extends HookConsumerWidget {
if (!playlist.containsTrack(track.value)) ...[ if (!playlist.containsTrack(track.value)) ...[
PopupMenuItem( PopupMenuItem(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
onTap: () { onTap: () async {
playback.addTrack(track.value); await playback.addTrack(track.value);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text( content: Text(
@ -326,6 +327,7 @@ class TrackTile extends HookConsumerWidget {
), ),
), ),
); );
}
}, },
child: ListTile( child: ListTile(
leading: const Icon(SpotubeIcons.queueAdd), leading: const Icon(SpotubeIcons.queueAdd),
@ -373,21 +375,21 @@ class TrackTile extends HookConsumerWidget {
title: Text(context.l10n.remove_from_queue), title: Text(context.l10n.remove_from_queue),
), ),
), ),
if (toggler.item3.hasData) if (toggler.me.hasData)
PopupMenuItem( PopupMenuItem(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
onTap: () { onTap: () {
toggler.item2.mutate(toggler.item1); toggler.toggleTrackLike.mutate(toggler.isLiked);
}, },
child: ListTile( child: ListTile(
leading: toggler.item1 leading: toggler.isLiked
? const Icon( ? const Icon(
SpotubeIcons.heartFilled, SpotubeIcons.heartFilled,
color: Colors.pink, color: Colors.pink,
) )
: const Icon(SpotubeIcons.heart), : const Icon(SpotubeIcons.heart),
title: Text( title: Text(
toggler.item1 toggler.isLiked
? context.l10n.remove_from_favorites ? context.l10n.remove_from_favorites
: context.l10n.save_as_favorite, : context.l10n.save_as_favorite,
), ),

View File

@ -1,3 +1,4 @@
import 'package:collection/collection.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/models/current_playlist.dart'; import 'package:spotube/models/current_playlist.dart';
@ -56,21 +57,22 @@ class BlackListNotifier
} }
bool contains(TrackSimple track) { bool contains(TrackSimple track) {
return filter([track]).isNotEmpty; final containsTrack =
} state.contains(BlacklistedElement.track(track.id!, track.name!));
Iterable<TrackSimple> filter(Iterable<TrackSimple> tracks) { final containsTrackArtists = track.artists?.any(
return tracks.where(
(track) {
return !state
.contains(BlacklistedElement.track(track.id!, track.name!)) &&
!(track.artists ?? []).any(
(artist) => state.contains( (artist) => state.contains(
BlacklistedElement.artist(artist.id!, artist.name!), BlacklistedElement.artist(artist.id!, artist.name!),
), ),
); ) ??
}, false;
).toList();
return containsTrack || containsTrackArtists;
}
/// Filters the non blacklisted tracks from the given [tracks]
Iterable<TrackSimple> filter(Iterable<TrackSimple> tracks) {
return tracks.whereNot(contains).toList();
} }
CurrentPlaylist filterPlaylist(CurrentPlaylist playlist) { CurrentPlaylist filterPlaylist(CurrentPlaylist playlist) {

View File

@ -21,7 +21,7 @@ import 'package:spotube/utils/type_conversion_utils.dart';
/// * [ ] Mixed Queue containing both [SpotubeTrack] and [LocalTrack] /// * [ ] Mixed Queue containing both [SpotubeTrack] and [LocalTrack]
/// * [ ] Modification of the Queue /// * [ ] Modification of the Queue
/// * [x] Add track at the end /// * [x] Add track at the end
/// * [ ] Add track at the beginning /// * [x] Add track at the beginning
/// * [x] Remove track /// * [x] Remove track
/// * [ ] Reorder track /// * [ ] Reorder track
/// * [ ] Caching and loading of cache of tracks /// * [ ] Caching and loading of cache of tracks
@ -277,7 +277,20 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
await audioPlayer.moveTrack(oldIndex, newIndex); await audioPlayer.moveTrack(oldIndex, newIndex);
} }
Future<void> addTracksAtFirst(Iterable<Track> track) async {} Future<void> addTracksAtFirst(Iterable<Track> tracks) async {
tracks = blacklist.filter(tracks).toList() as List<Track>;
final destIndex = state.active != null ? state.active! + 1 : 0;
final newTracks = state.tracks.toList()..insertAll(destIndex, tracks);
state = state.copyWith(tracks: newTracks.toSet());
tracks.forEachIndexed((index, track) async {
audioPlayer.addTrackAt(
makeAppropriateSource(track),
destIndex + index,
);
});
}
Future<void> populateSibling() async {} Future<void> populateSibling() async {}
Future<void> swapSibling(PipedSearchItem video) async {} Future<void> swapSibling(PipedSearchItem video) async {}

View File

@ -216,6 +216,16 @@ class SpotubeAudioPlayer extends AudioPlayerInterface
// } // }
} }
Future<void> addTrackAt(String url, int index) async {
final urlType = _resolveUrlType(url);
// if (mkSupportedPlatform && urlType is mk.Media) {
await _mkPlayer.insert(index, urlType as mk.Media);
// } else {
// await (_justAudio!.audioSource as ja.ConcatenatingAudioSource)
// .insert(index, urlType as ja.AudioSource);
// }
}
Future<void> removeTrack(int index) async { Future<void> removeTrack(int index) async {
// if (mkSupportedPlatform) { // if (mkSupportedPlatform) {
await _mkPlayer.remove(index); await _mkPlayer.remove(index);

View File

@ -243,6 +243,25 @@ class MkPlayerWithState extends Player {
} }
} }
FutureOr<void> insert(int index, Media media) {
if (_playlist == null ||
index < 0 ||
index > _playlist!.medias.length - 1) {
return null;
}
final newMedias = _playlist!.medias.toList()..insert(index, media);
playlist = _playlist!.copyWith(
medias: newMedias,
index: newMedias.indexOf(_playlist!.medias[_playlist!.index]),
);
if (shuffled && _tempMedias != null) {
_tempMedias!.insert(index, media);
}
}
/// Doesn't work when active media is the one to be removed /// Doesn't work when active media is the one to be removed
@override @override
FutureOr<void> remove(int index) async { FutureOr<void> remove(int index) async {

View File

@ -2,15 +2,17 @@ import 'package:fl_query/fl_query.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/hooks/use_spotify_query.dart'; import 'package:spotube/hooks/use_spotify_query.dart';
import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart';
class UserQueries { class UserQueries {
const UserQueries(); const UserQueries();
Query<User, dynamic> me(WidgetRef ref) { Query<User?, dynamic> me(WidgetRef ref) {
return useSpotifyQuery<User, dynamic>( return useSpotifyQuery<User, dynamic>(
"current-user", "current-user",
(spotify) async { (spotify) async {
final me = await spotify.me.get(); final me = await spotify.me.get();
if (ref.read(AuthenticationNotifier.provider) == null) return null;
if (me.images == null || me.images?.isEmpty == true) { if (me.images == null || me.images?.isEmpty == true) {
me.images = [ me.images = [
Image() Image()

View File

@ -1815,7 +1815,7 @@ packages:
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
tuple: tuple:
dependency: "direct main" dependency: transitive
description: description:
name: tuple name: tuple
sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa" sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa"

View File

@ -88,7 +88,6 @@ dependencies:
spotify: ^0.11.0 spotify: ^0.11.0
system_theme: ^2.1.0 system_theme: ^2.1.0
titlebar_buttons: ^1.0.0 titlebar_buttons: ^1.0.0
tuple: ^2.0.1
url_launcher: ^6.1.7 url_launcher: ^6.1.7
uuid: ^3.0.7 uuid: ^3.0.7
version: ^3.0.2 version: ^3.0.2