feat: add playback history provider

This commit is contained in:
Kingkor Roy Tirtho 2024-04-15 17:44:02 +06:00
parent 9e25c742d4
commit 2d9cd58fce
11 changed files with 235 additions and 64 deletions

View File

@ -10,6 +10,7 @@ import 'package:spotube/extensions/image.dart';
import 'package:spotube/extensions/track.dart'; import 'package:spotube/extensions/track.dart';
import 'package:spotube/models/connect/connect.dart'; import 'package:spotube/models/connect/connect.dart';
import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/connect/connect.dart';
import 'package:spotube/provider/history/history.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/spotify/spotify.dart';
import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/audio_player.dart';
@ -32,6 +33,7 @@ class AlbumCard extends HookConsumerWidget {
final playing = final playing =
useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying; useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying;
final playlistNotifier = ref.watch(proxyPlaylistProvider.notifier); final playlistNotifier = ref.watch(proxyPlaylistProvider.notifier);
final historyNotifier = ref.read(playbackHistoryProvider.notifier);
bool isPlaylistPlaying = useMemoized( bool isPlaylistPlaying = useMemoized(
() => playlist.containsCollection(album.id!), () => playlist.containsCollection(album.id!),
@ -87,6 +89,7 @@ class AlbumCard extends HookConsumerWidget {
} else { } else {
await playlistNotifier.load(fetchedTracks, autoPlay: true); await playlistNotifier.load(fetchedTracks, autoPlay: true);
playlistNotifier.addCollection(album.id!); playlistNotifier.addCollection(album.id!);
historyNotifier.addAlbums([album]);
} }
} finally { } finally {
updating.value = false; updating.value = false;
@ -104,6 +107,7 @@ class AlbumCard extends HookConsumerWidget {
if (fetchedTracks.isEmpty) return; if (fetchedTracks.isEmpty) return;
playlistNotifier.addTracks(fetchedTracks); playlistNotifier.addTracks(fetchedTracks);
playlistNotifier.addCollection(album.id!); playlistNotifier.addCollection(album.id!);
historyNotifier.addAlbums([album]);
if (context.mounted) { if (context.mounted) {
final snackbar = SnackBar( final snackbar = SnackBar(
content: Text( content: Text(

View File

@ -7,6 +7,7 @@ import 'package:spotube/components/shared/playbutton_card.dart';
import 'package:spotube/extensions/image.dart'; import 'package:spotube/extensions/image.dart';
import 'package:spotube/models/connect/connect.dart'; import 'package:spotube/models/connect/connect.dart';
import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/connect/connect.dart';
import 'package:spotube/provider/history/history.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/spotify/spotify.dart';
import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/audio_player.dart';
@ -22,6 +23,8 @@ class PlaylistCard extends HookConsumerWidget {
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final playlistQueue = ref.watch(proxyPlaylistProvider); final playlistQueue = ref.watch(proxyPlaylistProvider);
final playlistNotifier = ref.watch(proxyPlaylistProvider.notifier); final playlistNotifier = ref.watch(proxyPlaylistProvider.notifier);
final historyNotifier = ref.read(playbackHistoryProvider.notifier);
final playing = final playing =
useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying; useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying;
bool isPlaylistPlaying = useMemoized( bool isPlaylistPlaying = useMemoized(
@ -86,6 +89,7 @@ class PlaylistCard extends HookConsumerWidget {
} else { } else {
await playlistNotifier.load(fetchedTracks, autoPlay: true); await playlistNotifier.load(fetchedTracks, autoPlay: true);
playlistNotifier.addCollection(playlist.id!); playlistNotifier.addCollection(playlist.id!);
historyNotifier.addPlaylists([playlist]);
} }
} finally { } finally {
if (context.mounted) { if (context.mounted) {
@ -104,6 +108,7 @@ class PlaylistCard extends HookConsumerWidget {
playlistNotifier.addTracks(fetchedTracks); playlistNotifier.addTracks(fetchedTracks);
playlistNotifier.addCollection(playlist.id!); playlistNotifier.addCollection(playlist.id!);
historyNotifier.addPlaylists([playlist]);
if (context.mounted) { if (context.mounted) {
final snackbar = SnackBar( final snackbar = SnackBar(
content: Text("Added ${fetchedTracks.length} tracks to queue"), content: Text("Added ${fetchedTracks.length} tracks to queue"),

View File

@ -1,21 +1,6 @@
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
extension AlbumExtensions on AlbumSimple { extension AlbumExtensions on AlbumSimple {
Map<String, dynamic> toJson() {
return {
"albumType": albumType?.name,
"id": id,
"name": name,
"images": images
?.map((image) => {
"height": image.height,
"url": image.url,
"width": image.width,
})
.toList(),
};
}
Album toAlbum() { Album toAlbum() {
Album album = Album(); Album album = Album();
album.albumType = albumType; album.albumType = albumType;

View File

@ -1,17 +1,5 @@
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
extension ArtistJson on ArtistSimple {
Map<String, dynamic> toJson() {
return {
"href": href,
"id": id,
"name": name,
"type": type,
"uri": uri,
};
}
}
extension ArtistExtension on List<ArtistSimple> { extension ArtistExtension on List<ArtistSimple> {
String asString() { String asString() {
return map((e) => e.name?.replaceAll(",", " ")).join(", "); return map((e) => e.name?.replaceAll(",", " ")).join(", ");

View File

@ -3,8 +3,6 @@ import 'dart:io';
import 'package:metadata_god/metadata_god.dart'; import 'package:metadata_god/metadata_god.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/extensions/album_simple.dart';
import 'package:spotube/extensions/artist_simple.dart';
import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/audio_player.dart';
extension TrackExtensions on Track { extension TrackExtensions on Track {
@ -39,33 +37,6 @@ extension TrackExtensions on Track {
return this; return this;
} }
Map<String, dynamic> toJson() {
return TrackExtensions.trackToJson(this);
}
static Map<String, dynamic> trackToJson(Track track) {
return {
"album": track.album?.toJson(),
"artists": track.artists?.map((artist) => artist.toJson()).toList(),
"available_markets": track.availableMarkets?.map((e) => e.name).toList(),
"disc_number": track.discNumber,
"duration_ms": track.durationMs,
"explicit": track.explicit,
// "external_ids"track.: externalIds,
// "external_urls"track.: externalUrls,
"href": track.href,
"id": track.id,
"is_playable": track.isPlayable,
// "linked_from"track.: linkedFrom,
"name": track.name,
"popularity": track.popularity,
"preview_rrl": track.previewUrl,
"track_number": track.trackNumber,
"type": track.type,
"uri": track.uri,
};
}
} }
extension TrackSimpleExtensions on TrackSimple { extension TrackSimpleExtensions on TrackSimple {

View File

@ -1,5 +1,4 @@
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/extensions/track.dart';
class LocalTrack extends Track { class LocalTrack extends Track {
final String path; final String path;
@ -35,9 +34,10 @@ class LocalTrack extends Track {
); );
} }
@override
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
...TrackExtensions.trackToJson(this), ...super.toJson(),
'path': path, 'path': path,
}; };
} }

View File

@ -0,0 +1,83 @@
import 'dart:async';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/provider/history/state.dart';
import 'package:spotube/utils/persisted_state_notifier.dart';
class PlaybackHistoryState {
final List<PlaybackHistoryBase> items;
const PlaybackHistoryState({this.items = const []});
factory PlaybackHistoryState.fromJson(Map<String, dynamic> json) {
return PlaybackHistoryState(
items:
json["items"]?.map((json) => PlaybackHistoryBase.fromJson(json)));
}
Map<String, dynamic> toJson() {
return {
"items": items.map((s) => s.toJson()).toList(),
};
}
PlaybackHistoryState copyWith({
List<PlaybackHistoryBase>? items,
}) {
return PlaybackHistoryState(items: items ?? this.items);
}
}
class PlaybackHistoryNotifier
extends PersistedStateNotifier<PlaybackHistoryState> {
PlaybackHistoryNotifier()
: super(const PlaybackHistoryState(), "playback_history");
@override
FutureOr<PlaybackHistoryState> fromJson(Map<String, dynamic> json) =>
PlaybackHistoryState.fromJson(json);
@override
Map<String, dynamic> toJson() {
return state.toJson();
}
void addPlaylists(List<PlaylistSimple> playlists) {
state = state.copyWith(
items: [
...state.items,
for (final playlist in playlists)
PlaybackHistoryPlaylist(date: DateTime.now(), playlist: playlist),
],
);
}
void addAlbums(List<AlbumSimple> albums) {
state = state.copyWith(
items: [
...state.items,
for (final album in albums)
PlaybackHistoryAlbum(date: DateTime.now(), album: album),
],
);
}
void addTracks(List<TrackSimple> tracks) {
state = state.copyWith(
items: [
...state.items,
for (final track in tracks)
PlaybackHistoryTrack(date: DateTime.now(), track: track),
],
);
}
void clear() {
state = state.copyWith(items: []);
}
}
final playbackHistoryProvider =
StateNotifierProvider<PlaybackHistoryNotifier, PlaybackHistoryState>(
(ref) => PlaybackHistoryNotifier(),
);

View File

@ -0,0 +1,70 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:spotify/spotify.dart';
part 'state.g.dart';
@JsonSerializable()
class PlaybackHistoryBase {
final DateTime date;
const PlaybackHistoryBase({required this.date});
factory PlaybackHistoryBase.fromJson(Map<String, dynamic> json) {
if (json.containsKey("playlist")) {
return PlaybackHistoryPlaylist.fromJson(json);
} else if (json.containsKey("album")) {
return PlaybackHistoryAlbum.fromJson(json);
} else if (json.containsKey("track")) {
return PlaybackHistoryTrack.fromJson(json);
}
return _$PlaybackHistoryBaseFromJson(json);
}
Map<String, dynamic> toJson() => _$PlaybackHistoryBaseToJson(this);
}
@JsonSerializable()
class PlaybackHistoryPlaylist extends PlaybackHistoryBase {
final PlaylistSimple playlist;
PlaybackHistoryPlaylist({
required super.date,
required this.playlist,
});
factory PlaybackHistoryPlaylist.fromJson(Map<String, dynamic> json) =>
_$PlaybackHistoryPlaylistFromJson(json);
@override
Map<String, dynamic> toJson() => _$PlaybackHistoryPlaylistToJson(this);
}
@JsonSerializable()
class PlaybackHistoryAlbum extends PlaybackHistoryBase {
final AlbumSimple album;
PlaybackHistoryAlbum({
required super.date,
required this.album,
});
factory PlaybackHistoryAlbum.fromJson(Map<String, dynamic> json) =>
_$PlaybackHistoryAlbumFromJson(json);
@override
Map<String, dynamic> toJson() => _$PlaybackHistoryAlbumToJson(this);
}
@JsonSerializable()
class PlaybackHistoryTrack extends PlaybackHistoryBase {
final TrackSimple track;
PlaybackHistoryTrack({
required super.date,
required this.track,
});
factory PlaybackHistoryTrack.fromJson(Map<String, dynamic> json) =>
_$PlaybackHistoryTrackFromJson(json);
@override
Map<String, dynamic> toJson() => _$PlaybackHistoryTrackToJson(this);
}

View File

@ -0,0 +1,61 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'state.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
PlaybackHistoryBase _$PlaybackHistoryBaseFromJson(Map<String, dynamic> json) =>
PlaybackHistoryBase(
date: DateTime.parse(json['date'] as String),
);
Map<String, dynamic> _$PlaybackHistoryBaseToJson(
PlaybackHistoryBase instance) =>
<String, dynamic>{
'date': instance.date.toIso8601String(),
};
PlaybackHistoryPlaylist _$PlaybackHistoryPlaylistFromJson(
Map<String, dynamic> json) =>
PlaybackHistoryPlaylist(
date: DateTime.parse(json['date'] as String),
playlist:
PlaylistSimple.fromJson(json['playlist'] as Map<String, dynamic>),
);
Map<String, dynamic> _$PlaybackHistoryPlaylistToJson(
PlaybackHistoryPlaylist instance) =>
<String, dynamic>{
'date': instance.date.toIso8601String(),
'playlist': instance.playlist,
};
PlaybackHistoryAlbum _$PlaybackHistoryAlbumFromJson(
Map<String, dynamic> json) =>
PlaybackHistoryAlbum(
date: DateTime.parse(json['date'] as String),
album: AlbumSimple.fromJson(json['album'] as Map<String, dynamic>),
);
Map<String, dynamic> _$PlaybackHistoryAlbumToJson(
PlaybackHistoryAlbum instance) =>
<String, dynamic>{
'date': instance.date.toIso8601String(),
'album': instance.album,
};
PlaybackHistoryTrack _$PlaybackHistoryTrackFromJson(
Map<String, dynamic> json) =>
PlaybackHistoryTrack(
date: DateTime.parse(json['date'] as String),
track: TrackSimple.fromJson(json['track'] as Map<String, dynamic>),
);
Map<String, dynamic> _$PlaybackHistoryTrackToJson(
PlaybackHistoryTrack instance) =>
<String, dynamic>{
'date': instance.date.toIso8601String(),
'track': instance.track,
};

View File

@ -2105,11 +2105,12 @@ packages:
spotify: spotify:
dependency: "direct main" dependency: "direct main"
description: description:
name: spotify path: "."
sha256: "2308a84511c18ec1e72515a57e28abb1467389549d571c460732b4538c2e34de" ref: "feat/to-json"
url: "https://pub.dev" resolved-ref: "05ace91cdfe64db23d8c62077069e7c25b3645cb"
source: hosted url: "https://github.com/KRTirtho/spotify-dart.git"
version: "0.13.3" source: git
version: "0.13.5"
sqflite: sqflite:
dependency: transitive dependency: transitive
description: description:

View File

@ -122,7 +122,10 @@ dependencies:
flutter_sharing_intent: ^1.1.0 flutter_sharing_intent: ^1.1.0
flutter_broadcasts: ^0.4.0 flutter_broadcasts: ^0.4.0
freezed_annotation: ^2.4.1 freezed_annotation: ^2.4.1
spotify: ^0.13.3 spotify:
git:
url: https://github.com/KRTirtho/spotify-dart.git
ref: feat/to-json
bonsoir: ^5.1.9 bonsoir: ^5.1.9
shelf: ^1.4.1 shelf: ^1.4.1
shelf_router: ^1.1.4 shelf_router: ^1.1.4