mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-12-08 16:27:31 +00:00
feat: add playback history provider
This commit is contained in:
parent
9e25c742d4
commit
2d9cd58fce
@ -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(
|
||||||
|
|||||||
@ -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"),
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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(", ");
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
83
lib/provider/history/history.dart
Normal file
83
lib/provider/history/history.dart
Normal 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(),
|
||||||
|
);
|
||||||
70
lib/provider/history/state.dart
Normal file
70
lib/provider/history/state.dart
Normal 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);
|
||||||
|
}
|
||||||
61
lib/provider/history/state.g.dart
Normal file
61
lib/provider/history/state.g.dart
Normal 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,
|
||||||
|
};
|
||||||
11
pubspec.lock
11
pubspec.lock
@ -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:
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user