diff --git a/lib/components/shared/heart_button.dart b/lib/components/shared/heart_button.dart index 8ec376ade..b64ea6a6e 100644 --- a/lib/components/shared/heart_button.dart +++ b/lib/components/shared/heart_button.dart @@ -11,7 +11,6 @@ import 'package:spotube/services/mutations/mutations.dart'; import 'package:spotube/services/queries/queries.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; -import 'package:tuple/tuple.dart'; class HeartButton extends HookConsumerWidget { final bool isLiked; @@ -60,8 +59,11 @@ class HeartButton extends HookConsumerWidget { } } -Tuple3, Query> - useTrackToggleLike(Track track, WidgetRef ref) { +({ + bool isLiked, + Mutation toggleTrackLike, + Query me, +}) useTrackToggleLike(Track track, WidgetRef ref) { final me = useQueries.user.me(ref); final savedTracks = @@ -101,7 +103,7 @@ Tuple3, Query> }, ); - return Tuple3(isLiked, toggleTrackLike, me); + return (isLiked: isLiked, toggleTrackLike: toggleTrackLike, me: me); } class TrackHeartButton extends HookConsumerWidget { @@ -116,18 +118,18 @@ class TrackHeartButton extends HookConsumerWidget { final savedTracks = useQueries.playlist.tracksOfQuery(ref, "user-liked-tracks"); final toggler = useTrackToggleLike(track, ref); - if (toggler.item3.isLoading || !toggler.item3.hasData) { + if (toggler.me.isLoading || !toggler.me.hasData) { return const CircularProgressIndicator(); } return HeartButton( - tooltip: toggler.item1 + tooltip: toggler.isLiked ? context.l10n.remove_from_favorites : context.l10n.save_as_favorite, - isLiked: toggler.item1, + isLiked: toggler.isLiked, onPressed: savedTracks.hasData ? () { - toggler.item2.mutate(toggler.item1); + toggler.toggleTrackLike.mutate(toggler.isLiked); } : null, ); diff --git a/lib/components/shared/track_table/track_tile.dart b/lib/components/shared/track_table/track_tile.dart index 01998b83d..4b1e76c2b 100644 --- a/lib/components/shared/track_table/track_tile.dart +++ b/lib/components/shared/track_table/track_tile.dart @@ -316,16 +316,18 @@ class TrackTile extends HookConsumerWidget { if (!playlist.containsTrack(track.value)) ...[ PopupMenuItem( padding: EdgeInsets.zero, - onTap: () { - playback.addTrack(track.value); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n - .added_track_to_queue(track.value.name!), + onTap: () async { + await playback.addTrack(track.value); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + context.l10n + .added_track_to_queue(track.value.name!), + ), ), - ), - ); + ); + } }, child: ListTile( leading: const Icon(SpotubeIcons.queueAdd), @@ -373,21 +375,21 @@ class TrackTile extends HookConsumerWidget { title: Text(context.l10n.remove_from_queue), ), ), - if (toggler.item3.hasData) + if (toggler.me.hasData) PopupMenuItem( padding: EdgeInsets.zero, onTap: () { - toggler.item2.mutate(toggler.item1); + toggler.toggleTrackLike.mutate(toggler.isLiked); }, child: ListTile( - leading: toggler.item1 + leading: toggler.isLiked ? const Icon( SpotubeIcons.heartFilled, color: Colors.pink, ) : const Icon(SpotubeIcons.heart), title: Text( - toggler.item1 + toggler.isLiked ? context.l10n.remove_from_favorites : context.l10n.save_as_favorite, ), diff --git a/lib/provider/blacklist_provider.dart b/lib/provider/blacklist_provider.dart index 22abb8aea..363d4b4c9 100644 --- a/lib/provider/blacklist_provider.dart +++ b/lib/provider/blacklist_provider.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/models/current_playlist.dart'; @@ -56,21 +57,22 @@ class BlackListNotifier } bool contains(TrackSimple track) { - return filter([track]).isNotEmpty; + final containsTrack = + state.contains(BlacklistedElement.track(track.id!, track.name!)); + + final containsTrackArtists = track.artists?.any( + (artist) => state.contains( + BlacklistedElement.artist(artist.id!, artist.name!), + ), + ) ?? + false; + + return containsTrack || containsTrackArtists; } + /// Filters the non blacklisted tracks from the given [tracks] Iterable filter(Iterable tracks) { - return tracks.where( - (track) { - return !state - .contains(BlacklistedElement.track(track.id!, track.name!)) && - !(track.artists ?? []).any( - (artist) => state.contains( - BlacklistedElement.artist(artist.id!, artist.name!), - ), - ); - }, - ).toList(); + return tracks.whereNot(contains).toList(); } CurrentPlaylist filterPlaylist(CurrentPlaylist playlist) { diff --git a/lib/provider/proxy_playlist/proxy_playlist_provider.dart b/lib/provider/proxy_playlist/proxy_playlist_provider.dart index e0a13d3aa..7bc639f32 100644 --- a/lib/provider/proxy_playlist/proxy_playlist_provider.dart +++ b/lib/provider/proxy_playlist/proxy_playlist_provider.dart @@ -21,7 +21,7 @@ import 'package:spotube/utils/type_conversion_utils.dart'; /// * [ ] Mixed Queue containing both [SpotubeTrack] and [LocalTrack] /// * [ ] Modification of the Queue /// * [x] Add track at the end -/// * [ ] Add track at the beginning +/// * [x] Add track at the beginning /// * [x] Remove track /// * [ ] Reorder track /// * [ ] Caching and loading of cache of tracks @@ -277,7 +277,20 @@ class ProxyPlaylistNotifier extends StateNotifier await audioPlayer.moveTrack(oldIndex, newIndex); } - Future addTracksAtFirst(Iterable track) async {} + Future addTracksAtFirst(Iterable tracks) async { + tracks = blacklist.filter(tracks).toList() as List; + 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 populateSibling() async {} Future swapSibling(PipedSearchItem video) async {} diff --git a/lib/services/audio_player/audio_player_impl.dart b/lib/services/audio_player/audio_player_impl.dart index 770f00958..cca8c36c5 100644 --- a/lib/services/audio_player/audio_player_impl.dart +++ b/lib/services/audio_player/audio_player_impl.dart @@ -216,6 +216,16 @@ class SpotubeAudioPlayer extends AudioPlayerInterface // } } + Future 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 removeTrack(int index) async { // if (mkSupportedPlatform) { await _mkPlayer.remove(index); diff --git a/lib/services/audio_player/mk_state_player.dart b/lib/services/audio_player/mk_state_player.dart index 33f7308dc..51026b7f3 100644 --- a/lib/services/audio_player/mk_state_player.dart +++ b/lib/services/audio_player/mk_state_player.dart @@ -243,6 +243,25 @@ class MkPlayerWithState extends Player { } } + FutureOr 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 @override FutureOr remove(int index) async { diff --git a/lib/services/queries/user.dart b/lib/services/queries/user.dart index 6f94264d2..63d58afde 100644 --- a/lib/services/queries/user.dart +++ b/lib/services/queries/user.dart @@ -2,15 +2,17 @@ import 'package:fl_query/fl_query.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/hooks/use_spotify_query.dart'; +import 'package:spotube/provider/authentication_provider.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; class UserQueries { const UserQueries(); - Query me(WidgetRef ref) { + Query me(WidgetRef ref) { return useSpotifyQuery( "current-user", (spotify) async { final me = await spotify.me.get(); + if (ref.read(AuthenticationNotifier.provider) == null) return null; if (me.images == null || me.images?.isEmpty == true) { me.images = [ Image() diff --git a/pubspec.lock b/pubspec.lock index 79c438870..a5167848f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1815,7 +1815,7 @@ packages: source: hosted version: "1.0.0" tuple: - dependency: "direct main" + dependency: transitive description: name: tuple sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa" diff --git a/pubspec.yaml b/pubspec.yaml index 76e3a1132..e28a397df 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -88,7 +88,6 @@ dependencies: spotify: ^0.11.0 system_theme: ^2.1.0 titlebar_buttons: ^1.0.0 - tuple: ^2.0.1 url_launcher: ^6.1.7 uuid: ^3.0.7 version: ^3.0.2