feat: implement recently played section

This commit is contained in:
Kingkor Roy Tirtho 2024-04-28 11:12:17 +06:00
parent db209ac449
commit ddadb0edc5
40 changed files with 1513 additions and 317 deletions

View File

@ -2,4 +2,9 @@ targets:
$default:
sources:
exclude:
- bin/*.dart
- bin/*.dart
builders:
json_serializable:
options:
any_map: true
explicit_to_json: true

View File

@ -1,5 +1,4 @@
import 'package:spotify/spotify.dart';
import 'package:spotube/extensions/track.dart';
import 'package:spotube/models/spotify/home_feed.dart';
import 'package:spotube/models/spotify_friends.dart';

View File

@ -81,9 +81,9 @@ class AlbumCard extends HookConsumerWidget {
if (isRemoteDevice) {
final remotePlayback = ref.read(connectProvider.notifier);
await remotePlayback.load(
WebSocketLoadEventData(
WebSocketLoadEventData.album(
tracks: fetchedTracks,
collectionId: album.id!,
collection: album,
),
);
} else {

View File

@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
import 'package:spotube/provider/history/recent.dart';
import 'package:spotube/provider/history/state.dart';
class HomeRecentlyPlayedSection extends HookConsumerWidget {
const HomeRecentlyPlayedSection({super.key});
@override
Widget build(BuildContext context, ref) {
final history = ref.watch(recentlyPlayedItems);
if (history.isEmpty) {
return const SizedBox();
}
return HorizontalPlaybuttonCardView(
title: const Text('Recently Played'),
items: [
for (final item in history)
if (item is PlaybackHistoryPlaylist)
item.playlist
else if (item is PlaybackHistoryAlbum)
item.album
],
hasNextPage: false,
isLoadingNextPage: false,
onFetchMore: () {},
);
}
}

View File

@ -81,9 +81,9 @@ class PlaylistCard extends HookConsumerWidget {
if (isRemoteDevice) {
final remotePlayback = ref.read(connectProvider.notifier);
await remotePlayback.load(
WebSocketLoadEventData(
WebSocketLoadEventData.playlist(
tracks: fetchedTracks,
collectionId: playlist.id!,
collection: playlist,
),
);
} else {

View File

@ -96,7 +96,7 @@ class HorizontalPlaybuttonCardView<T> extends HookWidget {
return switch (item) {
PlaylistSimple() =>
PlaylistCard(item as PlaylistSimple),
AlbumSimple() => AlbumCard(item as Album),
AlbumSimple() => AlbumCard(item as AlbumSimple),
Artist() => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12.0),

View File

@ -17,6 +17,7 @@ import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
import 'package:spotube/components/shared/tracks_view/track_view_provider.dart';
import 'package:spotube/models/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/utils/service_utils.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
@ -28,6 +29,7 @@ class TrackViewBodySection extends HookConsumerWidget {
Widget build(BuildContext context, ref) {
final playlist = ref.watch(proxyPlaylistProvider);
final playlistNotifier = ref.watch(proxyPlaylistProvider.notifier);
final historyNotifier = ref.watch(playbackHistoryProvider.notifier);
final props = InheritedTrackView.of(context);
final trackViewState = ref.watch(trackViewProvider(props.tracks));
@ -146,11 +148,17 @@ class TrackViewBodySection extends HookConsumerWidget {
} else {
final tracks = await props.pagination.onFetchAll();
await remotePlayback.load(
WebSocketLoadEventData(
tracks: tracks,
collectionId: props.collectionId,
initialIndex: index,
),
props.collection is AlbumSimple
? WebSocketLoadEventData.album(
tracks: tracks,
collection: props.collection as AlbumSimple,
initialIndex: index,
)
: WebSocketLoadEventData.playlist(
tracks: tracks,
collection: props.collection as PlaylistSimple,
initialIndex: index,
),
);
}
} else {
@ -164,6 +172,13 @@ class TrackViewBodySection extends HookConsumerWidget {
autoPlay: true,
);
playlistNotifier.addCollection(props.collectionId);
if (props.collection is AlbumSimple) {
historyNotifier
.addAlbums([props.collection as AlbumSimple]);
} else {
historyNotifier
.addPlaylists([props.collection as PlaylistSimple]);
}
}
}
},

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/shared/adaptive/adaptive_pop_sheet_list.dart';
import 'package:spotube/components/shared/dialogs/confirm_download_dialog.dart';
@ -8,6 +9,7 @@ import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
import 'package:spotube/components/shared/tracks_view/track_view_provider.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/download_manager_provider.dart';
import 'package:spotube/provider/history/history.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
@ -23,6 +25,7 @@ class TrackViewBodyOptions extends HookConsumerWidget {
ref.watch(downloadManagerProvider);
final downloader = ref.watch(downloadManagerProvider.notifier);
final playlistNotifier = ref.watch(proxyPlaylistProvider.notifier);
final historyNotifier = ref.watch(playbackHistoryProvider.notifier);
final audioSource =
ref.watch(userPreferencesProvider.select((s) => s.audioSource));
@ -72,6 +75,12 @@ class TrackViewBodyOptions extends HookConsumerWidget {
{
playlistNotifier.addTracksAtFirst(selectedTracks);
playlistNotifier.addCollection(props.collectionId);
if (props.collection is AlbumSimple) {
historyNotifier.addAlbums([props.collection as AlbumSimple]);
} else {
historyNotifier
.addPlaylists([props.collection as PlaylistSimple]);
}
trackViewState.deselectAll();
break;
}
@ -79,6 +88,12 @@ class TrackViewBodyOptions extends HookConsumerWidget {
{
playlistNotifier.addTracks(selectedTracks);
playlistNotifier.addCollection(props.collectionId);
if (props.collection is AlbumSimple) {
historyNotifier.addAlbums([props.collection as AlbumSimple]);
} else {
historyNotifier
.addPlaylists([props.collection as PlaylistSimple]);
}
trackViewState.deselectAll();
break;
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/playlist/playlist_create_dialog.dart';
import 'package:spotube/components/shared/heart_button.dart';
@ -9,6 +10,7 @@ import 'package:spotube/components/shared/tracks_view/sections/body/use_is_user_
import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/provider/history/history.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
class TrackViewHeaderActions extends HookConsumerWidget {
@ -20,6 +22,7 @@ class TrackViewHeaderActions extends HookConsumerWidget {
final playlist = ref.watch(proxyPlaylistProvider);
final playlistNotifier = ref.watch(proxyPlaylistProvider.notifier);
final historyNotifier = ref.watch(playbackHistoryProvider.notifier);
final isActive = playlist.collections.contains(props.collectionId);
@ -61,6 +64,13 @@ class TrackViewHeaderActions extends HookConsumerWidget {
final tracks = await props.pagination.onFetchAll();
await playlistNotifier.addTracks(tracks);
playlistNotifier.addCollection(props.collectionId);
if (props.collection is AlbumSimple) {
historyNotifier
.addAlbums([props.collection as AlbumSimple]);
} else {
historyNotifier
.addPlaylists([props.collection as PlaylistSimple]);
}
},
),
if (props.onHeart != null && auth != null)

View File

@ -5,12 +5,14 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:palette_generator/palette_generator.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/shared/dialogs/select_device_dialog.dart';
import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/models/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/services/audio_player/audio_player.dart';
@ -28,6 +30,7 @@ class TrackViewHeaderButtons extends HookConsumerWidget {
final props = InheritedTrackView.of(context);
final playlist = ref.watch(proxyPlaylistProvider);
final playlistNotifier = ref.watch(proxyPlaylistProvider.notifier);
final historyNotifier = ref.watch(playbackHistoryProvider.notifier);
final isActive = playlist.collections.contains(props.collectionId);
@ -52,10 +55,16 @@ class TrackViewHeaderButtons extends HookConsumerWidget {
if (isRemoteDevice) {
final remotePlayback = ref.read(connectProvider.notifier);
await remotePlayback.load(
WebSocketLoadEventData(
tracks: allTracks,
collectionId: props.collectionId,
initialIndex: Random().nextInt(allTracks.length)),
props.collection is AlbumSimple
? WebSocketLoadEventData.album(
tracks: allTracks,
collection: props.collection as AlbumSimple,
initialIndex: Random().nextInt(allTracks.length))
: WebSocketLoadEventData.playlist(
tracks: allTracks,
collection: props.collection as PlaylistSimple,
initialIndex: Random().nextInt(allTracks.length),
),
);
await remotePlayback.setShuffle(true);
} else {
@ -66,6 +75,11 @@ class TrackViewHeaderButtons extends HookConsumerWidget {
);
await audioPlayer.setShuffle(true);
playlistNotifier.addCollection(props.collectionId);
if (props.collection is AlbumSimple) {
historyNotifier.addAlbums([props.collection as AlbumSimple]);
} else {
historyNotifier.addPlaylists([props.collection as PlaylistSimple]);
}
}
} finally {
isLoading.value = false;
@ -84,14 +98,24 @@ class TrackViewHeaderButtons extends HookConsumerWidget {
if (isRemoteDevice) {
final remotePlayback = ref.read(connectProvider.notifier);
await remotePlayback.load(
WebSocketLoadEventData(
tracks: allTracks,
collectionId: props.collectionId,
),
props.collection is AlbumSimple
? WebSocketLoadEventData.album(
tracks: allTracks,
collection: props.collection as AlbumSimple,
)
: WebSocketLoadEventData.playlist(
tracks: allTracks,
collection: props.collection as PlaylistSimple,
),
);
} else {
await playlistNotifier.load(allTracks, autoPlay: true);
playlistNotifier.addCollection(props.collectionId);
if (props.collection is AlbumSimple) {
historyNotifier.addAlbums([props.collection as AlbumSimple]);
} else {
historyNotifier.addPlaylists([props.collection as PlaylistSimple]);
}
}
} finally {
isLoading.value = false;

View File

@ -39,7 +39,7 @@ class PaginationProps {
}
class InheritedTrackView extends InheritedWidget {
final String collectionId;
final Object collection;
final String title;
final String? description;
final String image;
@ -55,7 +55,7 @@ class InheritedTrackView extends InheritedWidget {
const InheritedTrackView({
super.key,
required super.child,
required this.collectionId,
required this.collection,
required this.title,
this.description,
required this.image,
@ -65,7 +65,11 @@ class InheritedTrackView extends InheritedWidget {
required this.shareUrl,
this.isLiked = false,
this.onHeart,
});
}) : assert(collection is AlbumSimple || collection is PlaylistSimple);
String get collectionId => collection is AlbumSimple
? (collection as AlbumSimple).id!
: (collection as PlaylistSimple).id!;
@override
bool updateShouldNotify(InheritedTrackView oldWidget) {
@ -78,7 +82,7 @@ class InheritedTrackView extends InheritedWidget {
oldWidget.onHeart != onHeart ||
oldWidget.shareUrl != shareUrl ||
oldWidget.routePath != routePath ||
oldWidget.collectionId != collectionId ||
oldWidget.collection != collection ||
oldWidget.child != child;
}

View File

@ -16,16 +16,89 @@ final _privateConstructorUsedError = UnsupportedError(
WebSocketLoadEventData _$WebSocketLoadEventDataFromJson(
Map<String, dynamic> json) {
return _WebSocketLoadEventData.fromJson(json);
switch (json['runtimeType']) {
case 'playlist':
return WebSocketLoadEventDataPlaylist.fromJson(json);
case 'album':
return WebSocketLoadEventDataAlbum.fromJson(json);
default:
throw CheckedFromJsonException(
json,
'runtimeType',
'WebSocketLoadEventData',
'Invalid union type "${json['runtimeType']}"!');
}
}
/// @nodoc
mixin _$WebSocketLoadEventData {
@JsonKey(name: 'tracks', toJson: _tracksJson)
List<Track> get tracks => throw _privateConstructorUsedError;
String? get collectionId => throw _privateConstructorUsedError;
Object? get collection => throw _privateConstructorUsedError;
int? get initialIndex => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(
@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
PlaylistSimple? collection,
int? initialIndex)
playlist,
required TResult Function(
@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
AlbumSimple? collection,
int? initialIndex)
album,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(
@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
PlaylistSimple? collection,
int? initialIndex)?
playlist,
TResult? Function(
@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
AlbumSimple? collection,
int? initialIndex)?
album,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(
@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
PlaylistSimple? collection,
int? initialIndex)?
playlist,
TResult Function(
@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
AlbumSimple? collection,
int? initialIndex)?
album,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(WebSocketLoadEventDataPlaylist value) playlist,
required TResult Function(WebSocketLoadEventDataAlbum value) album,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(WebSocketLoadEventDataPlaylist value)? playlist,
TResult? Function(WebSocketLoadEventDataAlbum value)? album,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(WebSocketLoadEventDataPlaylist value)? playlist,
TResult Function(WebSocketLoadEventDataAlbum value)? album,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$WebSocketLoadEventDataCopyWith<WebSocketLoadEventData> get copyWith =>
@ -40,7 +113,6 @@ abstract class $WebSocketLoadEventDataCopyWith<$Res> {
@useResult
$Res call(
{@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
String? collectionId,
int? initialIndex});
}
@ -59,7 +131,6 @@ class _$WebSocketLoadEventDataCopyWithImpl<$Res,
@override
$Res call({
Object? tracks = null,
Object? collectionId = freezed,
Object? initialIndex = freezed,
}) {
return _then(_value.copyWith(
@ -67,10 +138,6 @@ class _$WebSocketLoadEventDataCopyWithImpl<$Res,
? _value.tracks
: tracks // ignore: cast_nullable_to_non_nullable
as List<Track>,
collectionId: freezed == collectionId
? _value.collectionId
: collectionId // ignore: cast_nullable_to_non_nullable
as String?,
initialIndex: freezed == initialIndex
? _value.initialIndex
: initialIndex // ignore: cast_nullable_to_non_nullable
@ -80,46 +147,46 @@ class _$WebSocketLoadEventDataCopyWithImpl<$Res,
}
/// @nodoc
abstract class _$$WebSocketLoadEventDataImplCopyWith<$Res>
abstract class _$$WebSocketLoadEventDataPlaylistImplCopyWith<$Res>
implements $WebSocketLoadEventDataCopyWith<$Res> {
factory _$$WebSocketLoadEventDataImplCopyWith(
_$WebSocketLoadEventDataImpl value,
$Res Function(_$WebSocketLoadEventDataImpl) then) =
__$$WebSocketLoadEventDataImplCopyWithImpl<$Res>;
factory _$$WebSocketLoadEventDataPlaylistImplCopyWith(
_$WebSocketLoadEventDataPlaylistImpl value,
$Res Function(_$WebSocketLoadEventDataPlaylistImpl) then) =
__$$WebSocketLoadEventDataPlaylistImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
String? collectionId,
PlaylistSimple? collection,
int? initialIndex});
}
/// @nodoc
class __$$WebSocketLoadEventDataImplCopyWithImpl<$Res>
class __$$WebSocketLoadEventDataPlaylistImplCopyWithImpl<$Res>
extends _$WebSocketLoadEventDataCopyWithImpl<$Res,
_$WebSocketLoadEventDataImpl>
implements _$$WebSocketLoadEventDataImplCopyWith<$Res> {
__$$WebSocketLoadEventDataImplCopyWithImpl(
_$WebSocketLoadEventDataImpl _value,
$Res Function(_$WebSocketLoadEventDataImpl) _then)
_$WebSocketLoadEventDataPlaylistImpl>
implements _$$WebSocketLoadEventDataPlaylistImplCopyWith<$Res> {
__$$WebSocketLoadEventDataPlaylistImplCopyWithImpl(
_$WebSocketLoadEventDataPlaylistImpl _value,
$Res Function(_$WebSocketLoadEventDataPlaylistImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? tracks = null,
Object? collectionId = freezed,
Object? collection = freezed,
Object? initialIndex = freezed,
}) {
return _then(_$WebSocketLoadEventDataImpl(
return _then(_$WebSocketLoadEventDataPlaylistImpl(
tracks: null == tracks
? _value._tracks
: tracks // ignore: cast_nullable_to_non_nullable
as List<Track>,
collectionId: freezed == collectionId
? _value.collectionId
: collectionId // ignore: cast_nullable_to_non_nullable
as String?,
collection: freezed == collection
? _value.collection
: collection // ignore: cast_nullable_to_non_nullable
as PlaylistSimple?,
initialIndex: freezed == initialIndex
? _value.initialIndex
: initialIndex // ignore: cast_nullable_to_non_nullable
@ -130,16 +197,21 @@ class __$$WebSocketLoadEventDataImplCopyWithImpl<$Res>
/// @nodoc
@JsonSerializable()
class _$WebSocketLoadEventDataImpl implements _WebSocketLoadEventData {
_$WebSocketLoadEventDataImpl(
class _$WebSocketLoadEventDataPlaylistImpl
extends WebSocketLoadEventDataPlaylist {
_$WebSocketLoadEventDataPlaylistImpl(
{@JsonKey(name: 'tracks', toJson: _tracksJson)
required final List<Track> tracks,
this.collectionId,
this.initialIndex})
: _tracks = tracks;
this.collection,
this.initialIndex,
final String? $type})
: _tracks = tracks,
$type = $type ?? 'playlist',
super._();
factory _$WebSocketLoadEventDataImpl.fromJson(Map<String, dynamic> json) =>
_$$WebSocketLoadEventDataImplFromJson(json);
factory _$WebSocketLoadEventDataPlaylistImpl.fromJson(
Map<String, dynamic> json) =>
_$$WebSocketLoadEventDataPlaylistImplFromJson(json);
final List<Track> _tracks;
@override
@ -151,23 +223,26 @@ class _$WebSocketLoadEventDataImpl implements _WebSocketLoadEventData {
}
@override
final String? collectionId;
final PlaylistSimple? collection;
@override
final int? initialIndex;
@JsonKey(name: 'runtimeType')
final String $type;
@override
String toString() {
return 'WebSocketLoadEventData(tracks: $tracks, collectionId: $collectionId, initialIndex: $initialIndex)';
return 'WebSocketLoadEventData.playlist(tracks: $tracks, collection: $collection, initialIndex: $initialIndex)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$WebSocketLoadEventDataImpl &&
other is _$WebSocketLoadEventDataPlaylistImpl &&
const DeepCollectionEquality().equals(other._tracks, _tracks) &&
(identical(other.collectionId, collectionId) ||
other.collectionId == collectionId) &&
(identical(other.collection, collection) ||
other.collection == collection) &&
(identical(other.initialIndex, initialIndex) ||
other.initialIndex == initialIndex));
}
@ -175,42 +250,361 @@ class _$WebSocketLoadEventDataImpl implements _WebSocketLoadEventData {
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType,
const DeepCollectionEquality().hash(_tracks), collectionId, initialIndex);
const DeepCollectionEquality().hash(_tracks), collection, initialIndex);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$WebSocketLoadEventDataImplCopyWith<_$WebSocketLoadEventDataImpl>
get copyWith => __$$WebSocketLoadEventDataImplCopyWithImpl<
_$WebSocketLoadEventDataImpl>(this, _$identity);
_$$WebSocketLoadEventDataPlaylistImplCopyWith<
_$WebSocketLoadEventDataPlaylistImpl>
get copyWith => __$$WebSocketLoadEventDataPlaylistImplCopyWithImpl<
_$WebSocketLoadEventDataPlaylistImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(
@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
PlaylistSimple? collection,
int? initialIndex)
playlist,
required TResult Function(
@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
AlbumSimple? collection,
int? initialIndex)
album,
}) {
return playlist(tracks, collection, initialIndex);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(
@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
PlaylistSimple? collection,
int? initialIndex)?
playlist,
TResult? Function(
@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
AlbumSimple? collection,
int? initialIndex)?
album,
}) {
return playlist?.call(tracks, collection, initialIndex);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(
@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
PlaylistSimple? collection,
int? initialIndex)?
playlist,
TResult Function(
@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
AlbumSimple? collection,
int? initialIndex)?
album,
required TResult orElse(),
}) {
if (playlist != null) {
return playlist(tracks, collection, initialIndex);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(WebSocketLoadEventDataPlaylist value) playlist,
required TResult Function(WebSocketLoadEventDataAlbum value) album,
}) {
return playlist(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(WebSocketLoadEventDataPlaylist value)? playlist,
TResult? Function(WebSocketLoadEventDataAlbum value)? album,
}) {
return playlist?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(WebSocketLoadEventDataPlaylist value)? playlist,
TResult Function(WebSocketLoadEventDataAlbum value)? album,
required TResult orElse(),
}) {
if (playlist != null) {
return playlist(this);
}
return orElse();
}
@override
Map<String, dynamic> toJson() {
return _$$WebSocketLoadEventDataImplToJson(
return _$$WebSocketLoadEventDataPlaylistImplToJson(
this,
);
}
}
abstract class _WebSocketLoadEventData implements WebSocketLoadEventData {
factory _WebSocketLoadEventData(
abstract class WebSocketLoadEventDataPlaylist extends WebSocketLoadEventData {
factory WebSocketLoadEventDataPlaylist(
{@JsonKey(name: 'tracks', toJson: _tracksJson)
required final List<Track> tracks,
final String? collectionId,
final int? initialIndex}) = _$WebSocketLoadEventDataImpl;
final PlaylistSimple? collection,
final int? initialIndex}) = _$WebSocketLoadEventDataPlaylistImpl;
WebSocketLoadEventDataPlaylist._() : super._();
factory _WebSocketLoadEventData.fromJson(Map<String, dynamic> json) =
_$WebSocketLoadEventDataImpl.fromJson;
factory WebSocketLoadEventDataPlaylist.fromJson(Map<String, dynamic> json) =
_$WebSocketLoadEventDataPlaylistImpl.fromJson;
@override
@JsonKey(name: 'tracks', toJson: _tracksJson)
List<Track> get tracks;
@override
String? get collectionId;
PlaylistSimple? get collection;
@override
int? get initialIndex;
@override
@JsonKey(ignore: true)
_$$WebSocketLoadEventDataImplCopyWith<_$WebSocketLoadEventDataImpl>
_$$WebSocketLoadEventDataPlaylistImplCopyWith<
_$WebSocketLoadEventDataPlaylistImpl>
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$WebSocketLoadEventDataAlbumImplCopyWith<$Res>
implements $WebSocketLoadEventDataCopyWith<$Res> {
factory _$$WebSocketLoadEventDataAlbumImplCopyWith(
_$WebSocketLoadEventDataAlbumImpl value,
$Res Function(_$WebSocketLoadEventDataAlbumImpl) then) =
__$$WebSocketLoadEventDataAlbumImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
AlbumSimple? collection,
int? initialIndex});
}
/// @nodoc
class __$$WebSocketLoadEventDataAlbumImplCopyWithImpl<$Res>
extends _$WebSocketLoadEventDataCopyWithImpl<$Res,
_$WebSocketLoadEventDataAlbumImpl>
implements _$$WebSocketLoadEventDataAlbumImplCopyWith<$Res> {
__$$WebSocketLoadEventDataAlbumImplCopyWithImpl(
_$WebSocketLoadEventDataAlbumImpl _value,
$Res Function(_$WebSocketLoadEventDataAlbumImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? tracks = null,
Object? collection = freezed,
Object? initialIndex = freezed,
}) {
return _then(_$WebSocketLoadEventDataAlbumImpl(
tracks: null == tracks
? _value._tracks
: tracks // ignore: cast_nullable_to_non_nullable
as List<Track>,
collection: freezed == collection
? _value.collection
: collection // ignore: cast_nullable_to_non_nullable
as AlbumSimple?,
initialIndex: freezed == initialIndex
? _value.initialIndex
: initialIndex // ignore: cast_nullable_to_non_nullable
as int?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$WebSocketLoadEventDataAlbumImpl extends WebSocketLoadEventDataAlbum {
_$WebSocketLoadEventDataAlbumImpl(
{@JsonKey(name: 'tracks', toJson: _tracksJson)
required final List<Track> tracks,
this.collection,
this.initialIndex,
final String? $type})
: _tracks = tracks,
$type = $type ?? 'album',
super._();
factory _$WebSocketLoadEventDataAlbumImpl.fromJson(
Map<String, dynamic> json) =>
_$$WebSocketLoadEventDataAlbumImplFromJson(json);
final List<Track> _tracks;
@override
@JsonKey(name: 'tracks', toJson: _tracksJson)
List<Track> get tracks {
if (_tracks is EqualUnmodifiableListView) return _tracks;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_tracks);
}
@override
final AlbumSimple? collection;
@override
final int? initialIndex;
@JsonKey(name: 'runtimeType')
final String $type;
@override
String toString() {
return 'WebSocketLoadEventData.album(tracks: $tracks, collection: $collection, initialIndex: $initialIndex)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$WebSocketLoadEventDataAlbumImpl &&
const DeepCollectionEquality().equals(other._tracks, _tracks) &&
(identical(other.collection, collection) ||
other.collection == collection) &&
(identical(other.initialIndex, initialIndex) ||
other.initialIndex == initialIndex));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType,
const DeepCollectionEquality().hash(_tracks), collection, initialIndex);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$WebSocketLoadEventDataAlbumImplCopyWith<_$WebSocketLoadEventDataAlbumImpl>
get copyWith => __$$WebSocketLoadEventDataAlbumImplCopyWithImpl<
_$WebSocketLoadEventDataAlbumImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(
@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
PlaylistSimple? collection,
int? initialIndex)
playlist,
required TResult Function(
@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
AlbumSimple? collection,
int? initialIndex)
album,
}) {
return album(tracks, collection, initialIndex);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(
@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
PlaylistSimple? collection,
int? initialIndex)?
playlist,
TResult? Function(
@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
AlbumSimple? collection,
int? initialIndex)?
album,
}) {
return album?.call(tracks, collection, initialIndex);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(
@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
PlaylistSimple? collection,
int? initialIndex)?
playlist,
TResult Function(
@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
AlbumSimple? collection,
int? initialIndex)?
album,
required TResult orElse(),
}) {
if (album != null) {
return album(tracks, collection, initialIndex);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(WebSocketLoadEventDataPlaylist value) playlist,
required TResult Function(WebSocketLoadEventDataAlbum value) album,
}) {
return album(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(WebSocketLoadEventDataPlaylist value)? playlist,
TResult? Function(WebSocketLoadEventDataAlbum value)? album,
}) {
return album?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(WebSocketLoadEventDataPlaylist value)? playlist,
TResult Function(WebSocketLoadEventDataAlbum value)? album,
required TResult orElse(),
}) {
if (album != null) {
return album(this);
}
return orElse();
}
@override
Map<String, dynamic> toJson() {
return _$$WebSocketLoadEventDataAlbumImplToJson(
this,
);
}
}
abstract class WebSocketLoadEventDataAlbum extends WebSocketLoadEventData {
factory WebSocketLoadEventDataAlbum(
{@JsonKey(name: 'tracks', toJson: _tracksJson)
required final List<Track> tracks,
final AlbumSimple? collection,
final int? initialIndex}) = _$WebSocketLoadEventDataAlbumImpl;
WebSocketLoadEventDataAlbum._() : super._();
factory WebSocketLoadEventDataAlbum.fromJson(Map<String, dynamic> json) =
_$WebSocketLoadEventDataAlbumImpl.fromJson;
@override
@JsonKey(name: 'tracks', toJson: _tracksJson)
List<Track> get tracks;
@override
AlbumSimple? get collection;
@override
int? get initialIndex;
@override
@JsonKey(ignore: true)
_$$WebSocketLoadEventDataAlbumImplCopyWith<_$WebSocketLoadEventDataAlbumImpl>
get copyWith => throw _privateConstructorUsedError;
}

View File

@ -6,20 +6,48 @@ part of 'connect.dart';
// JsonSerializableGenerator
// **************************************************************************
_$WebSocketLoadEventDataImpl _$$WebSocketLoadEventDataImplFromJson(
Map<String, dynamic> json) =>
_$WebSocketLoadEventDataImpl(
tracks: (json['tracks'] as List<dynamic>)
.map((e) => Track.fromJson(e as Map<String, dynamic>))
.toList(),
collectionId: json['collectionId'] as String?,
initialIndex: json['initialIndex'] as int?,
);
_$WebSocketLoadEventDataPlaylistImpl
_$$WebSocketLoadEventDataPlaylistImplFromJson(Map json) =>
_$WebSocketLoadEventDataPlaylistImpl(
tracks: (json['tracks'] as List<dynamic>)
.map((e) => Track.fromJson(Map<String, dynamic>.from(e as Map)))
.toList(),
collection: json['collection'] == null
? null
: PlaylistSimple.fromJson(
Map<String, dynamic>.from(json['collection'] as Map)),
initialIndex: json['initialIndex'] as int?,
$type: json['runtimeType'] as String?,
);
Map<String, dynamic> _$$WebSocketLoadEventDataImplToJson(
_$WebSocketLoadEventDataImpl instance) =>
Map<String, dynamic> _$$WebSocketLoadEventDataPlaylistImplToJson(
_$WebSocketLoadEventDataPlaylistImpl instance) =>
<String, dynamic>{
'tracks': _tracksJson(instance.tracks),
'collectionId': instance.collectionId,
'collection': instance.collection?.toJson(),
'initialIndex': instance.initialIndex,
'runtimeType': instance.$type,
};
_$WebSocketLoadEventDataAlbumImpl _$$WebSocketLoadEventDataAlbumImplFromJson(
Map json) =>
_$WebSocketLoadEventDataAlbumImpl(
tracks: (json['tracks'] as List<dynamic>)
.map((e) => Track.fromJson(Map<String, dynamic>.from(e as Map)))
.toList(),
collection: json['collection'] == null
? null
: AlbumSimple.fromJson(
Map<String, dynamic>.from(json['collection'] as Map)),
initialIndex: json['initialIndex'] as int?,
$type: json['runtimeType'] as String?,
);
Map<String, dynamic> _$$WebSocketLoadEventDataAlbumImplToJson(
_$WebSocketLoadEventDataAlbumImpl instance) =>
<String, dynamic>{
'tracks': _tracksJson(instance.tracks),
'collection': instance.collection?.toJson(),
'initialIndex': instance.initialIndex,
'runtimeType': instance.$type,
};

View File

@ -6,14 +6,27 @@ List<Map<String, dynamic>> _tracksJson(List<Track> tracks) {
@freezed
class WebSocketLoadEventData with _$WebSocketLoadEventData {
factory WebSocketLoadEventData({
const WebSocketLoadEventData._();
factory WebSocketLoadEventData.playlist({
@JsonKey(name: 'tracks', toJson: _tracksJson) required List<Track> tracks,
String? collectionId,
PlaylistSimple? collection,
int? initialIndex,
}) = _WebSocketLoadEventData;
}) = WebSocketLoadEventDataPlaylist;
factory WebSocketLoadEventData.album({
@JsonKey(name: 'tracks', toJson: _tracksJson) required List<Track> tracks,
AlbumSimple? collection,
int? initialIndex,
}) = WebSocketLoadEventDataAlbum;
factory WebSocketLoadEventData.fromJson(Map<String, dynamic> json) =>
_$WebSocketLoadEventDataFromJson(json);
String? get collectionId => when(
playlist: (tracks, collection, _) => collection?.id,
album: (tracks, collection, _) => collection?.id,
);
}
class WebSocketLoadEvent extends WebSocketEvent<WebSocketLoadEventData> {

View File

@ -97,7 +97,7 @@ class SourceTypeAdapter extends TypeAdapter<SourceType> {
// JsonSerializableGenerator
// **************************************************************************
SourceMatch _$SourceMatchFromJson(Map<String, dynamic> json) => SourceMatch(
SourceMatch _$SourceMatchFromJson(Map json) => SourceMatch(
id: json['id'] as String,
sourceId: json['sourceId'] as String,
sourceType: $enumDecode(_$SourceTypeEnumMap, json['sourceType']),

View File

@ -6,14 +6,13 @@ part of 'home_feed.dart';
// JsonSerializableGenerator
// **************************************************************************
_$SpotifySectionPlaylistImpl _$$SpotifySectionPlaylistImplFromJson(
Map<String, dynamic> json) =>
_$SpotifySectionPlaylistImpl _$$SpotifySectionPlaylistImplFromJson(Map json) =>
_$SpotifySectionPlaylistImpl(
description: json['description'] as String,
format: json['format'] as String,
images: (json['images'] as List<dynamic>)
.map((e) =>
SpotifySectionItemImage.fromJson(e as Map<String, dynamic>))
.map((e) => SpotifySectionItemImage.fromJson(
Map<String, dynamic>.from(e as Map)))
.toList(),
name: json['name'] as String,
owner: json['owner'] as String,
@ -25,20 +24,19 @@ Map<String, dynamic> _$$SpotifySectionPlaylistImplToJson(
<String, dynamic>{
'description': instance.description,
'format': instance.format,
'images': instance.images,
'images': instance.images.map((e) => e.toJson()).toList(),
'name': instance.name,
'owner': instance.owner,
'uri': instance.uri,
};
_$SpotifySectionArtistImpl _$$SpotifySectionArtistImplFromJson(
Map<String, dynamic> json) =>
_$SpotifySectionArtistImpl _$$SpotifySectionArtistImplFromJson(Map json) =>
_$SpotifySectionArtistImpl(
name: json['name'] as String,
uri: json['uri'] as String,
images: (json['images'] as List<dynamic>)
.map((e) =>
SpotifySectionItemImage.fromJson(e as Map<String, dynamic>))
.map((e) => SpotifySectionItemImage.fromJson(
Map<String, dynamic>.from(e as Map)))
.toList(),
);
@ -47,19 +45,18 @@ Map<String, dynamic> _$$SpotifySectionArtistImplToJson(
<String, dynamic>{
'name': instance.name,
'uri': instance.uri,
'images': instance.images,
'images': instance.images.map((e) => e.toJson()).toList(),
};
_$SpotifySectionAlbumImpl _$$SpotifySectionAlbumImplFromJson(
Map<String, dynamic> json) =>
_$SpotifySectionAlbumImpl _$$SpotifySectionAlbumImplFromJson(Map json) =>
_$SpotifySectionAlbumImpl(
artists: (json['artists'] as List<dynamic>)
.map((e) =>
SpotifySectionAlbumArtist.fromJson(e as Map<String, dynamic>))
.map((e) => SpotifySectionAlbumArtist.fromJson(
Map<String, dynamic>.from(e as Map)))
.toList(),
images: (json['images'] as List<dynamic>)
.map((e) =>
SpotifySectionItemImage.fromJson(e as Map<String, dynamic>))
.map((e) => SpotifySectionItemImage.fromJson(
Map<String, dynamic>.from(e as Map)))
.toList(),
name: json['name'] as String,
uri: json['uri'] as String,
@ -68,14 +65,14 @@ _$SpotifySectionAlbumImpl _$$SpotifySectionAlbumImplFromJson(
Map<String, dynamic> _$$SpotifySectionAlbumImplToJson(
_$SpotifySectionAlbumImpl instance) =>
<String, dynamic>{
'artists': instance.artists,
'images': instance.images,
'artists': instance.artists.map((e) => e.toJson()).toList(),
'images': instance.images.map((e) => e.toJson()).toList(),
'name': instance.name,
'uri': instance.uri,
};
_$SpotifySectionAlbumArtistImpl _$$SpotifySectionAlbumArtistImplFromJson(
Map<String, dynamic> json) =>
Map json) =>
_$SpotifySectionAlbumArtistImpl(
name: json['name'] as String,
uri: json['uri'] as String,
@ -89,7 +86,7 @@ Map<String, dynamic> _$$SpotifySectionAlbumArtistImplToJson(
};
_$SpotifySectionItemImageImpl _$$SpotifySectionItemImageImplFromJson(
Map<String, dynamic> json) =>
Map json) =>
_$SpotifySectionItemImageImpl(
height: json['height'] as num?,
url: json['url'] as String,
@ -105,40 +102,40 @@ Map<String, dynamic> _$$SpotifySectionItemImageImplToJson(
};
_$SpotifyHomeFeedSectionItemImpl _$$SpotifyHomeFeedSectionItemImplFromJson(
Map<String, dynamic> json) =>
Map json) =>
_$SpotifyHomeFeedSectionItemImpl(
typename: json['typename'] as String,
playlist: json['playlist'] == null
? null
: SpotifySectionPlaylist.fromJson(
json['playlist'] as Map<String, dynamic>),
Map<String, dynamic>.from(json['playlist'] as Map)),
artist: json['artist'] == null
? null
: SpotifySectionArtist.fromJson(
json['artist'] as Map<String, dynamic>),
Map<String, dynamic>.from(json['artist'] as Map)),
album: json['album'] == null
? null
: SpotifySectionAlbum.fromJson(json['album'] as Map<String, dynamic>),
: SpotifySectionAlbum.fromJson(
Map<String, dynamic>.from(json['album'] as Map)),
);
Map<String, dynamic> _$$SpotifyHomeFeedSectionItemImplToJson(
_$SpotifyHomeFeedSectionItemImpl instance) =>
<String, dynamic>{
'typename': instance.typename,
'playlist': instance.playlist,
'artist': instance.artist,
'album': instance.album,
'playlist': instance.playlist?.toJson(),
'artist': instance.artist?.toJson(),
'album': instance.album?.toJson(),
};
_$SpotifyHomeFeedSectionImpl _$$SpotifyHomeFeedSectionImplFromJson(
Map<String, dynamic> json) =>
_$SpotifyHomeFeedSectionImpl _$$SpotifyHomeFeedSectionImplFromJson(Map json) =>
_$SpotifyHomeFeedSectionImpl(
typename: json['typename'] as String,
title: json['title'] as String?,
uri: json['uri'] as String,
items: (json['items'] as List<dynamic>)
.map((e) =>
SpotifyHomeFeedSectionItem.fromJson(e as Map<String, dynamic>))
.map((e) => SpotifyHomeFeedSectionItem.fromJson(
Map<String, dynamic>.from(e as Map)))
.toList(),
);
@ -148,16 +145,15 @@ Map<String, dynamic> _$$SpotifyHomeFeedSectionImplToJson(
'typename': instance.typename,
'title': instance.title,
'uri': instance.uri,
'items': instance.items,
'items': instance.items.map((e) => e.toJson()).toList(),
};
_$SpotifyHomeFeedImpl _$$SpotifyHomeFeedImplFromJson(
Map<String, dynamic> json) =>
_$SpotifyHomeFeedImpl _$$SpotifyHomeFeedImplFromJson(Map json) =>
_$SpotifyHomeFeedImpl(
greeting: json['greeting'] as String,
sections: (json['sections'] as List<dynamic>)
.map(
(e) => SpotifyHomeFeedSection.fromJson(e as Map<String, dynamic>))
.map((e) => SpotifyHomeFeedSection.fromJson(
Map<String, dynamic>.from(e as Map)))
.toList(),
);
@ -165,5 +161,5 @@ Map<String, dynamic> _$$SpotifyHomeFeedImplToJson(
_$SpotifyHomeFeedImpl instance) =>
<String, dynamic>{
'greeting': instance.greeting,
'sections': instance.sections,
'sections': instance.sections.map((e) => e.toJson()).toList(),
};

View File

@ -6,8 +6,7 @@ part of 'recommendation_seeds.dart';
// JsonSerializableGenerator
// **************************************************************************
_$RecommendationSeedsImpl _$$RecommendationSeedsImplFromJson(
Map<String, dynamic> json) =>
_$RecommendationSeedsImpl _$$RecommendationSeedsImplFromJson(Map json) =>
_$RecommendationSeedsImpl(
acousticness: json['acousticness'] as num?,
danceability: json['danceability'] as num?,

View File

@ -6,60 +6,55 @@ part of 'spotify_friends.dart';
// JsonSerializableGenerator
// **************************************************************************
SpotifyFriend _$SpotifyFriendFromJson(Map<String, dynamic> json) =>
SpotifyFriend(
SpotifyFriend _$SpotifyFriendFromJson(Map json) => SpotifyFriend(
uri: json['uri'] as String,
name: json['name'] as String,
imageUrl: json['imageUrl'] as String,
);
SpotifyActivityArtist _$SpotifyActivityArtistFromJson(
Map<String, dynamic> json) =>
SpotifyActivityArtist _$SpotifyActivityArtistFromJson(Map json) =>
SpotifyActivityArtist(
uri: json['uri'] as String,
name: json['name'] as String,
);
SpotifyActivityAlbum _$SpotifyActivityAlbumFromJson(
Map<String, dynamic> json) =>
SpotifyActivityAlbum _$SpotifyActivityAlbumFromJson(Map json) =>
SpotifyActivityAlbum(
uri: json['uri'] as String,
name: json['name'] as String,
);
SpotifyActivityContext _$SpotifyActivityContextFromJson(
Map<String, dynamic> json) =>
SpotifyActivityContext _$SpotifyActivityContextFromJson(Map json) =>
SpotifyActivityContext(
uri: json['uri'] as String,
name: json['name'] as String,
index: json['index'] as num,
);
SpotifyActivityTrack _$SpotifyActivityTrackFromJson(
Map<String, dynamic> json) =>
SpotifyActivityTrack _$SpotifyActivityTrackFromJson(Map json) =>
SpotifyActivityTrack(
uri: json['uri'] as String,
name: json['name'] as String,
imageUrl: json['imageUrl'] as String,
artist: SpotifyActivityArtist.fromJson(
json['artist'] as Map<String, dynamic>),
album:
SpotifyActivityAlbum.fromJson(json['album'] as Map<String, dynamic>),
Map<String, dynamic>.from(json['artist'] as Map)),
album: SpotifyActivityAlbum.fromJson(
Map<String, dynamic>.from(json['album'] as Map)),
context: SpotifyActivityContext.fromJson(
json['context'] as Map<String, dynamic>),
Map<String, dynamic>.from(json['context'] as Map)),
);
SpotifyFriendActivity _$SpotifyFriendActivityFromJson(
Map<String, dynamic> json) =>
SpotifyFriendActivity _$SpotifyFriendActivityFromJson(Map json) =>
SpotifyFriendActivity(
user: SpotifyFriend.fromJson(json['user'] as Map<String, dynamic>),
track:
SpotifyActivityTrack.fromJson(json['track'] as Map<String, dynamic>),
user: SpotifyFriend.fromJson(
Map<String, dynamic>.from(json['user'] as Map)),
track: SpotifyActivityTrack.fromJson(
Map<String, dynamic>.from(json['track'] as Map)),
);
SpotifyFriends _$SpotifyFriendsFromJson(Map<String, dynamic> json) =>
SpotifyFriends(
SpotifyFriends _$SpotifyFriendsFromJson(Map json) => SpotifyFriends(
friends: (json['friends'] as List<dynamic>)
.map((e) => SpotifyFriendActivity.fromJson(e as Map<String, dynamic>))
.map((e) => SpotifyFriendActivity.fromJson(
Map<String, dynamic>.from(e as Map)))
.toList(),
);

View File

@ -22,7 +22,7 @@ class AlbumPage extends HookConsumerWidget {
final isSavedAlbum = ref.watch(albumsIsSavedProvider(album.id!));
return InheritedTrackView(
collectionId: album.id!,
collection: album,
image: album.images.asUrlString(
placeholder: ImagePlaceholder.albumArt,
),

View File

@ -52,8 +52,9 @@ class ArtistPageTopTracks extends HookConsumerWidget {
if (!isPlaylistPlaying) {
await remotePlayback.load(
WebSocketLoadEventData(
WebSocketLoadEventData.playlist(
tracks: tracks,
collection: null,
initialIndex: tracks.indexWhere((s) => s.id == currentTrack?.id),
),
);

View File

@ -10,6 +10,7 @@ import 'package:spotube/components/home/sections/friends.dart';
import 'package:spotube/components/home/sections/genres.dart';
import 'package:spotube/components/home/sections/made_for_user.dart';
import 'package:spotube/components/home/sections/new_releases.dart';
import 'package:spotube/components/home/sections/recent.dart';
import 'package:spotube/components/shared/image/universal_image.dart';
import 'package:spotube/components/shared/page_window_title_bar.dart';
import 'package:spotube/extensions/constrains.dart';
@ -72,6 +73,8 @@ class HomePage extends HookConsumerWidget {
else if (kIsMacOS)
const SliverGap(10),
const HomeGenresSection(),
const SliverGap(10),
const SliverToBoxAdapter(child: HomeRecentlyPlayedSection()),
const SliverToBoxAdapter(child: HomeFeaturedSection()),
const HomePageFriendsSection(),
const SliverToBoxAdapter(child: HomeNewReleasesSection()),

View File

@ -18,7 +18,7 @@ class LikedPlaylistPage extends HookConsumerWidget {
final tracks = likedTracks.asData?.value ?? <Track>[];
return InheritedTrackView(
collectionId: playlist.id!,
collection: playlist,
image: "assets/liked-tracks.jpg",
pagination: PaginationProps(
hasNextPage: false,

View File

@ -29,7 +29,7 @@ class PlaylistPage extends HookConsumerWidget {
final isUserPlaylist = useIsUserPlaylist(ref, playlist.id!);
return InheritedTrackView(
collectionId: playlist.id!,
collection: playlist,
image: playlist.images.asUrlString(
placeholder: ImagePlaceholder.collection,
),

View File

@ -76,7 +76,7 @@ class SearchTracksSection extends HookConsumerWidget {
if (shouldPlay) {
await remotePlayback.load(
WebSocketLoadEventData(
WebSocketLoadEventData.playlist(
tracks: [track],
),
);

View File

@ -9,9 +9,11 @@ import 'package:shelf/shelf_io.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shelf_router/shelf_router.dart';
import 'package:shelf_web_socket/shelf_web_socket.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/models/connect/connect.dart';
import 'package:spotube/models/logger.dart';
import 'package:spotube/provider/connect/clients.dart';
import 'package:spotube/provider/history/history.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/services/audio_player/audio_player.dart';
@ -32,6 +34,7 @@ final connectServerProvider = FutureProvider((ref) async {
final resolvedService = await ref
.watch(connectClientsProvider.selectAsync((s) => s.resolvedService));
final playbackNotifier = ref.read(proxyPlaylistProvider.notifier);
final historyNotifier = ref.read(playbackHistoryProvider.notifier);
if (!enabled || resolvedService != null) {
return null;
@ -146,8 +149,14 @@ final connectServerProvider = FutureProvider((ref) async {
initialIndex: event.data.initialIndex ?? 0,
);
if (event.data.collectionId != null) {
playbackNotifier.addCollection(event.data.collectionId!);
if (event.data.collectionId == null) return;
playbackNotifier.addCollection(event.data.collectionId!);
if (event.data.collection is AlbumSimple) {
historyNotifier
.addAlbums([event.data.collection as AlbumSimple]);
} else {
historyNotifier.addPlaylists(
[event.data.collection as PlaylistSimple]);
}
});

View File

@ -6,13 +6,19 @@ import 'package:spotube/provider/history/state.dart';
import 'package:spotube/utils/persisted_state_notifier.dart';
class PlaybackHistoryState {
final List<PlaybackHistoryBase> items;
final List<PlaybackHistoryItem> items;
const PlaybackHistoryState({this.items = const []});
factory PlaybackHistoryState.fromJson(Map<String, dynamic> json) {
return PlaybackHistoryState(
items:
json["items"]?.map((json) => PlaybackHistoryBase.fromJson(json)));
items: json["items"]
?.map(
(json) => PlaybackHistoryItem.fromJson(json),
)
.toList()
.cast<PlaybackHistoryItem>() ??
<PlaybackHistoryItem>[],
);
}
Map<String, dynamic> toJson() {
@ -22,7 +28,7 @@ class PlaybackHistoryState {
}
PlaybackHistoryState copyWith({
List<PlaybackHistoryBase>? items,
List<PlaybackHistoryItem>? items,
}) {
return PlaybackHistoryState(items: items ?? this.items);
}
@ -47,7 +53,8 @@ class PlaybackHistoryNotifier
items: [
...state.items,
for (final playlist in playlists)
PlaybackHistoryPlaylist(date: DateTime.now(), playlist: playlist),
PlaybackHistoryItem.playlist(
date: DateTime.now(), playlist: playlist),
],
);
}
@ -57,7 +64,7 @@ class PlaybackHistoryNotifier
items: [
...state.items,
for (final album in albums)
PlaybackHistoryAlbum(date: DateTime.now(), album: album),
PlaybackHistoryItem.album(date: DateTime.now(), album: album),
],
);
}
@ -67,7 +74,7 @@ class PlaybackHistoryNotifier
items: [
...state.items,
for (final track in tracks)
PlaybackHistoryTrack(date: DateTime.now(), track: track),
PlaybackHistoryItem.track(date: DateTime.now(), track: track),
],
);
}

View File

@ -0,0 +1,40 @@
import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/provider/history/history.dart';
import 'package:spotube/provider/history/state.dart';
final recentlyPlayedItems = Provider((ref) {
return ref.watch(
playbackHistoryProvider.select(
(s) => s.items
.toSet()
// unique items
.whereIndexed(
(index, item) =>
index ==
s.items.lastIndexWhere(
(e) => switch ((e, item)) {
(
PlaybackHistoryPlaylist(:final playlist),
PlaybackHistoryPlaylist(playlist: final playlist2)
) =>
playlist.id == playlist2.id,
(
PlaybackHistoryAlbum(:final album),
PlaybackHistoryAlbum(album: final album2)
) =>
album.id == album2.id,
_ => false,
},
),
)
.where(
(s) => s is PlaybackHistoryPlaylist || s is PlaybackHistoryAlbum,
)
.take(10)
.sortedBy((s) => s.date)
.reversed
.toList(),
),
);
});

View File

@ -1,70 +1,26 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:spotify/spotify.dart';
part 'state.freezed.dart';
part 'state.g.dart';
@JsonSerializable()
class PlaybackHistoryBase {
final DateTime date;
@freezed
class PlaybackHistoryItem with _$PlaybackHistoryItem {
factory PlaybackHistoryItem.playlist({
required DateTime date,
required PlaylistSimple playlist,
}) = PlaybackHistoryPlaylist;
const PlaybackHistoryBase({required this.date});
factory PlaybackHistoryItem.album({
required DateTime date,
required AlbumSimple album,
}) = PlaybackHistoryAlbum;
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);
}
factory PlaybackHistoryItem.track({
required DateTime date,
required TrackSimple track,
}) = PlaybackHistoryTrack;
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);
factory PlaybackHistoryItem.fromJson(Map<String, dynamic> json) =>
_$PlaybackHistoryItemFromJson(json);
}

View File

@ -0,0 +1,644 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'state.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
PlaybackHistoryItem _$PlaybackHistoryItemFromJson(Map<String, dynamic> json) {
switch (json['runtimeType']) {
case 'playlist':
return PlaybackHistoryPlaylist.fromJson(json);
case 'album':
return PlaybackHistoryAlbum.fromJson(json);
case 'track':
return PlaybackHistoryTrack.fromJson(json);
default:
throw CheckedFromJsonException(json, 'runtimeType', 'PlaybackHistoryItem',
'Invalid union type "${json['runtimeType']}"!');
}
}
/// @nodoc
mixin _$PlaybackHistoryItem {
DateTime get date => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(DateTime date, PlaylistSimple playlist) playlist,
required TResult Function(DateTime date, AlbumSimple album) album,
required TResult Function(DateTime date, TrackSimple track) track,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(DateTime date, PlaylistSimple playlist)? playlist,
TResult? Function(DateTime date, AlbumSimple album)? album,
TResult? Function(DateTime date, TrackSimple track)? track,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(DateTime date, PlaylistSimple playlist)? playlist,
TResult Function(DateTime date, AlbumSimple album)? album,
TResult Function(DateTime date, TrackSimple track)? track,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(PlaybackHistoryPlaylist value) playlist,
required TResult Function(PlaybackHistoryAlbum value) album,
required TResult Function(PlaybackHistoryTrack value) track,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(PlaybackHistoryPlaylist value)? playlist,
TResult? Function(PlaybackHistoryAlbum value)? album,
TResult? Function(PlaybackHistoryTrack value)? track,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(PlaybackHistoryPlaylist value)? playlist,
TResult Function(PlaybackHistoryAlbum value)? album,
TResult Function(PlaybackHistoryTrack value)? track,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$PlaybackHistoryItemCopyWith<PlaybackHistoryItem> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $PlaybackHistoryItemCopyWith<$Res> {
factory $PlaybackHistoryItemCopyWith(
PlaybackHistoryItem value, $Res Function(PlaybackHistoryItem) then) =
_$PlaybackHistoryItemCopyWithImpl<$Res, PlaybackHistoryItem>;
@useResult
$Res call({DateTime date});
}
/// @nodoc
class _$PlaybackHistoryItemCopyWithImpl<$Res, $Val extends PlaybackHistoryItem>
implements $PlaybackHistoryItemCopyWith<$Res> {
_$PlaybackHistoryItemCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? date = null,
}) {
return _then(_value.copyWith(
date: null == date
? _value.date
: date // ignore: cast_nullable_to_non_nullable
as DateTime,
) as $Val);
}
}
/// @nodoc
abstract class _$$PlaybackHistoryPlaylistImplCopyWith<$Res>
implements $PlaybackHistoryItemCopyWith<$Res> {
factory _$$PlaybackHistoryPlaylistImplCopyWith(
_$PlaybackHistoryPlaylistImpl value,
$Res Function(_$PlaybackHistoryPlaylistImpl) then) =
__$$PlaybackHistoryPlaylistImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({DateTime date, PlaylistSimple playlist});
}
/// @nodoc
class __$$PlaybackHistoryPlaylistImplCopyWithImpl<$Res>
extends _$PlaybackHistoryItemCopyWithImpl<$Res,
_$PlaybackHistoryPlaylistImpl>
implements _$$PlaybackHistoryPlaylistImplCopyWith<$Res> {
__$$PlaybackHistoryPlaylistImplCopyWithImpl(
_$PlaybackHistoryPlaylistImpl _value,
$Res Function(_$PlaybackHistoryPlaylistImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? date = null,
Object? playlist = null,
}) {
return _then(_$PlaybackHistoryPlaylistImpl(
date: null == date
? _value.date
: date // ignore: cast_nullable_to_non_nullable
as DateTime,
playlist: null == playlist
? _value.playlist
: playlist // ignore: cast_nullable_to_non_nullable
as PlaylistSimple,
));
}
}
/// @nodoc
@JsonSerializable()
class _$PlaybackHistoryPlaylistImpl implements PlaybackHistoryPlaylist {
_$PlaybackHistoryPlaylistImpl(
{required this.date, required this.playlist, final String? $type})
: $type = $type ?? 'playlist';
factory _$PlaybackHistoryPlaylistImpl.fromJson(Map<String, dynamic> json) =>
_$$PlaybackHistoryPlaylistImplFromJson(json);
@override
final DateTime date;
@override
final PlaylistSimple playlist;
@JsonKey(name: 'runtimeType')
final String $type;
@override
String toString() {
return 'PlaybackHistoryItem.playlist(date: $date, playlist: $playlist)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$PlaybackHistoryPlaylistImpl &&
(identical(other.date, date) || other.date == date) &&
(identical(other.playlist, playlist) ||
other.playlist == playlist));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, date, playlist);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$PlaybackHistoryPlaylistImplCopyWith<_$PlaybackHistoryPlaylistImpl>
get copyWith => __$$PlaybackHistoryPlaylistImplCopyWithImpl<
_$PlaybackHistoryPlaylistImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(DateTime date, PlaylistSimple playlist) playlist,
required TResult Function(DateTime date, AlbumSimple album) album,
required TResult Function(DateTime date, TrackSimple track) track,
}) {
return playlist(date, this.playlist);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(DateTime date, PlaylistSimple playlist)? playlist,
TResult? Function(DateTime date, AlbumSimple album)? album,
TResult? Function(DateTime date, TrackSimple track)? track,
}) {
return playlist?.call(date, this.playlist);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(DateTime date, PlaylistSimple playlist)? playlist,
TResult Function(DateTime date, AlbumSimple album)? album,
TResult Function(DateTime date, TrackSimple track)? track,
required TResult orElse(),
}) {
if (playlist != null) {
return playlist(date, this.playlist);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(PlaybackHistoryPlaylist value) playlist,
required TResult Function(PlaybackHistoryAlbum value) album,
required TResult Function(PlaybackHistoryTrack value) track,
}) {
return playlist(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(PlaybackHistoryPlaylist value)? playlist,
TResult? Function(PlaybackHistoryAlbum value)? album,
TResult? Function(PlaybackHistoryTrack value)? track,
}) {
return playlist?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(PlaybackHistoryPlaylist value)? playlist,
TResult Function(PlaybackHistoryAlbum value)? album,
TResult Function(PlaybackHistoryTrack value)? track,
required TResult orElse(),
}) {
if (playlist != null) {
return playlist(this);
}
return orElse();
}
@override
Map<String, dynamic> toJson() {
return _$$PlaybackHistoryPlaylistImplToJson(
this,
);
}
}
abstract class PlaybackHistoryPlaylist implements PlaybackHistoryItem {
factory PlaybackHistoryPlaylist(
{required final DateTime date,
required final PlaylistSimple playlist}) = _$PlaybackHistoryPlaylistImpl;
factory PlaybackHistoryPlaylist.fromJson(Map<String, dynamic> json) =
_$PlaybackHistoryPlaylistImpl.fromJson;
@override
DateTime get date;
PlaylistSimple get playlist;
@override
@JsonKey(ignore: true)
_$$PlaybackHistoryPlaylistImplCopyWith<_$PlaybackHistoryPlaylistImpl>
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$PlaybackHistoryAlbumImplCopyWith<$Res>
implements $PlaybackHistoryItemCopyWith<$Res> {
factory _$$PlaybackHistoryAlbumImplCopyWith(_$PlaybackHistoryAlbumImpl value,
$Res Function(_$PlaybackHistoryAlbumImpl) then) =
__$$PlaybackHistoryAlbumImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({DateTime date, AlbumSimple album});
}
/// @nodoc
class __$$PlaybackHistoryAlbumImplCopyWithImpl<$Res>
extends _$PlaybackHistoryItemCopyWithImpl<$Res, _$PlaybackHistoryAlbumImpl>
implements _$$PlaybackHistoryAlbumImplCopyWith<$Res> {
__$$PlaybackHistoryAlbumImplCopyWithImpl(_$PlaybackHistoryAlbumImpl _value,
$Res Function(_$PlaybackHistoryAlbumImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? date = null,
Object? album = null,
}) {
return _then(_$PlaybackHistoryAlbumImpl(
date: null == date
? _value.date
: date // ignore: cast_nullable_to_non_nullable
as DateTime,
album: null == album
? _value.album
: album // ignore: cast_nullable_to_non_nullable
as AlbumSimple,
));
}
}
/// @nodoc
@JsonSerializable()
class _$PlaybackHistoryAlbumImpl implements PlaybackHistoryAlbum {
_$PlaybackHistoryAlbumImpl(
{required this.date, required this.album, final String? $type})
: $type = $type ?? 'album';
factory _$PlaybackHistoryAlbumImpl.fromJson(Map<String, dynamic> json) =>
_$$PlaybackHistoryAlbumImplFromJson(json);
@override
final DateTime date;
@override
final AlbumSimple album;
@JsonKey(name: 'runtimeType')
final String $type;
@override
String toString() {
return 'PlaybackHistoryItem.album(date: $date, album: $album)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$PlaybackHistoryAlbumImpl &&
(identical(other.date, date) || other.date == date) &&
(identical(other.album, album) || other.album == album));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, date, album);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$PlaybackHistoryAlbumImplCopyWith<_$PlaybackHistoryAlbumImpl>
get copyWith =>
__$$PlaybackHistoryAlbumImplCopyWithImpl<_$PlaybackHistoryAlbumImpl>(
this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(DateTime date, PlaylistSimple playlist) playlist,
required TResult Function(DateTime date, AlbumSimple album) album,
required TResult Function(DateTime date, TrackSimple track) track,
}) {
return album(date, this.album);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(DateTime date, PlaylistSimple playlist)? playlist,
TResult? Function(DateTime date, AlbumSimple album)? album,
TResult? Function(DateTime date, TrackSimple track)? track,
}) {
return album?.call(date, this.album);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(DateTime date, PlaylistSimple playlist)? playlist,
TResult Function(DateTime date, AlbumSimple album)? album,
TResult Function(DateTime date, TrackSimple track)? track,
required TResult orElse(),
}) {
if (album != null) {
return album(date, this.album);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(PlaybackHistoryPlaylist value) playlist,
required TResult Function(PlaybackHistoryAlbum value) album,
required TResult Function(PlaybackHistoryTrack value) track,
}) {
return album(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(PlaybackHistoryPlaylist value)? playlist,
TResult? Function(PlaybackHistoryAlbum value)? album,
TResult? Function(PlaybackHistoryTrack value)? track,
}) {
return album?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(PlaybackHistoryPlaylist value)? playlist,
TResult Function(PlaybackHistoryAlbum value)? album,
TResult Function(PlaybackHistoryTrack value)? track,
required TResult orElse(),
}) {
if (album != null) {
return album(this);
}
return orElse();
}
@override
Map<String, dynamic> toJson() {
return _$$PlaybackHistoryAlbumImplToJson(
this,
);
}
}
abstract class PlaybackHistoryAlbum implements PlaybackHistoryItem {
factory PlaybackHistoryAlbum(
{required final DateTime date,
required final AlbumSimple album}) = _$PlaybackHistoryAlbumImpl;
factory PlaybackHistoryAlbum.fromJson(Map<String, dynamic> json) =
_$PlaybackHistoryAlbumImpl.fromJson;
@override
DateTime get date;
AlbumSimple get album;
@override
@JsonKey(ignore: true)
_$$PlaybackHistoryAlbumImplCopyWith<_$PlaybackHistoryAlbumImpl>
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$PlaybackHistoryTrackImplCopyWith<$Res>
implements $PlaybackHistoryItemCopyWith<$Res> {
factory _$$PlaybackHistoryTrackImplCopyWith(_$PlaybackHistoryTrackImpl value,
$Res Function(_$PlaybackHistoryTrackImpl) then) =
__$$PlaybackHistoryTrackImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({DateTime date, TrackSimple track});
}
/// @nodoc
class __$$PlaybackHistoryTrackImplCopyWithImpl<$Res>
extends _$PlaybackHistoryItemCopyWithImpl<$Res, _$PlaybackHistoryTrackImpl>
implements _$$PlaybackHistoryTrackImplCopyWith<$Res> {
__$$PlaybackHistoryTrackImplCopyWithImpl(_$PlaybackHistoryTrackImpl _value,
$Res Function(_$PlaybackHistoryTrackImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? date = null,
Object? track = null,
}) {
return _then(_$PlaybackHistoryTrackImpl(
date: null == date
? _value.date
: date // ignore: cast_nullable_to_non_nullable
as DateTime,
track: null == track
? _value.track
: track // ignore: cast_nullable_to_non_nullable
as TrackSimple,
));
}
}
/// @nodoc
@JsonSerializable()
class _$PlaybackHistoryTrackImpl implements PlaybackHistoryTrack {
_$PlaybackHistoryTrackImpl(
{required this.date, required this.track, final String? $type})
: $type = $type ?? 'track';
factory _$PlaybackHistoryTrackImpl.fromJson(Map<String, dynamic> json) =>
_$$PlaybackHistoryTrackImplFromJson(json);
@override
final DateTime date;
@override
final TrackSimple track;
@JsonKey(name: 'runtimeType')
final String $type;
@override
String toString() {
return 'PlaybackHistoryItem.track(date: $date, track: $track)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$PlaybackHistoryTrackImpl &&
(identical(other.date, date) || other.date == date) &&
(identical(other.track, track) || other.track == track));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, date, track);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$PlaybackHistoryTrackImplCopyWith<_$PlaybackHistoryTrackImpl>
get copyWith =>
__$$PlaybackHistoryTrackImplCopyWithImpl<_$PlaybackHistoryTrackImpl>(
this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(DateTime date, PlaylistSimple playlist) playlist,
required TResult Function(DateTime date, AlbumSimple album) album,
required TResult Function(DateTime date, TrackSimple track) track,
}) {
return track(date, this.track);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(DateTime date, PlaylistSimple playlist)? playlist,
TResult? Function(DateTime date, AlbumSimple album)? album,
TResult? Function(DateTime date, TrackSimple track)? track,
}) {
return track?.call(date, this.track);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(DateTime date, PlaylistSimple playlist)? playlist,
TResult Function(DateTime date, AlbumSimple album)? album,
TResult Function(DateTime date, TrackSimple track)? track,
required TResult orElse(),
}) {
if (track != null) {
return track(date, this.track);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(PlaybackHistoryPlaylist value) playlist,
required TResult Function(PlaybackHistoryAlbum value) album,
required TResult Function(PlaybackHistoryTrack value) track,
}) {
return track(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(PlaybackHistoryPlaylist value)? playlist,
TResult? Function(PlaybackHistoryAlbum value)? album,
TResult? Function(PlaybackHistoryTrack value)? track,
}) {
return track?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(PlaybackHistoryPlaylist value)? playlist,
TResult Function(PlaybackHistoryAlbum value)? album,
TResult Function(PlaybackHistoryTrack value)? track,
required TResult orElse(),
}) {
if (track != null) {
return track(this);
}
return orElse();
}
@override
Map<String, dynamic> toJson() {
return _$$PlaybackHistoryTrackImplToJson(
this,
);
}
}
abstract class PlaybackHistoryTrack implements PlaybackHistoryItem {
factory PlaybackHistoryTrack(
{required final DateTime date,
required final TrackSimple track}) = _$PlaybackHistoryTrackImpl;
factory PlaybackHistoryTrack.fromJson(Map<String, dynamic> json) =
_$PlaybackHistoryTrackImpl.fromJson;
@override
DateTime get date;
TrackSimple get track;
@override
@JsonKey(ignore: true)
_$$PlaybackHistoryTrackImplCopyWith<_$PlaybackHistoryTrackImpl>
get copyWith => throw _privateConstructorUsedError;
}

View File

@ -6,56 +6,51 @@ part of 'state.dart';
// JsonSerializableGenerator
// **************************************************************************
PlaybackHistoryBase _$PlaybackHistoryBaseFromJson(Map<String, dynamic> json) =>
PlaybackHistoryBase(
_$PlaybackHistoryPlaylistImpl _$$PlaybackHistoryPlaylistImplFromJson(
Map json) =>
_$PlaybackHistoryPlaylistImpl(
date: DateTime.parse(json['date'] as String),
playlist: PlaylistSimple.fromJson(
Map<String, dynamic>.from(json['playlist'] as Map)),
$type: json['runtimeType'] as String?,
);
Map<String, dynamic> _$PlaybackHistoryBaseToJson(
PlaybackHistoryBase instance) =>
Map<String, dynamic> _$$PlaybackHistoryPlaylistImplToJson(
_$PlaybackHistoryPlaylistImpl instance) =>
<String, dynamic>{
'date': instance.date.toIso8601String(),
'playlist': instance.playlist.toJson(),
'runtimeType': instance.$type,
};
PlaybackHistoryPlaylist _$PlaybackHistoryPlaylistFromJson(
Map<String, dynamic> json) =>
PlaybackHistoryPlaylist(
_$PlaybackHistoryAlbumImpl _$$PlaybackHistoryAlbumImplFromJson(Map json) =>
_$PlaybackHistoryAlbumImpl(
date: DateTime.parse(json['date'] as String),
playlist:
PlaylistSimple.fromJson(json['playlist'] as Map<String, dynamic>),
album:
AlbumSimple.fromJson(Map<String, dynamic>.from(json['album'] as Map)),
$type: json['runtimeType'] as String?,
);
Map<String, dynamic> _$PlaybackHistoryPlaylistToJson(
PlaybackHistoryPlaylist instance) =>
Map<String, dynamic> _$$PlaybackHistoryAlbumImplToJson(
_$PlaybackHistoryAlbumImpl instance) =>
<String, dynamic>{
'date': instance.date.toIso8601String(),
'playlist': instance.playlist,
'album': instance.album.toJson(),
'runtimeType': instance.$type,
};
PlaybackHistoryAlbum _$PlaybackHistoryAlbumFromJson(
Map<String, dynamic> json) =>
PlaybackHistoryAlbum(
_$PlaybackHistoryTrackImpl _$$PlaybackHistoryTrackImplFromJson(Map json) =>
_$PlaybackHistoryTrackImpl(
date: DateTime.parse(json['date'] as String),
album: AlbumSimple.fromJson(json['album'] as Map<String, dynamic>),
track:
TrackSimple.fromJson(Map<String, dynamic>.from(json['track'] as Map)),
$type: json['runtimeType'] as String?,
);
Map<String, dynamic> _$PlaybackHistoryAlbumToJson(
PlaybackHistoryAlbum instance) =>
Map<String, dynamic> _$$PlaybackHistoryTrackImplToJson(
_$PlaybackHistoryTrackImpl 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,
'track': instance.track.toJson(),
'runtimeType': instance.$type,
};

View File

@ -3,24 +3,50 @@
import 'dart:async';
import 'package:catcher_2/catcher_2.dart';
import 'package:palette_generator/palette_generator.dart';
import 'package:spotube/components/shared/image/universal_image.dart';
import 'package:spotube/extensions/image.dart';
import 'package:spotube/models/local_track.dart';
import 'package:spotube/provider/palette_provider.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/provider/proxy_playlist/skip_segments.dart';
import 'package:spotube/provider/server/sourced_track.dart';
import 'package:spotube/services/audio_player/audio_player.dart';
extension ProxyPlaylistListeners on ProxyPlaylistNotifier {
Future<void> updatePalette() async {
final palette = ref.read(paletteProvider);
if (!preferences.albumColorSync) {
if (palette != null) ref.read(paletteProvider.notifier).state = null;
return;
}
return Future.microtask(() async {
if (playlist.activeTrack == null) return;
final palette = await PaletteGenerator.fromImageProvider(
UniversalImage.imageProvider(
(playlist.activeTrack?.album?.images).asUrlString(
placeholder: ImagePlaceholder.albumArt,
),
height: 50,
width: 50,
),
);
ref.read(paletteProvider.notifier).state = palette;
});
}
StreamSubscription subscribeToPlaylist() {
return audioPlayer.playlistStream.listen((playlist) {
state = state.copyWith(
tracks: playlist.medias
return audioPlayer.playlistStream.listen((mpvPlaylist) {
state = playlist.copyWith(
tracks: mpvPlaylist.medias
.map((media) => SpotubeMedia.fromMedia(media).track)
.toSet(),
active: playlist.index,
active: mpvPlaylist.index,
);
notificationService.addTrack(state.activeTrack!);
discord.updatePresence(state.activeTrack!);
notificationService.addTrack(playlist.activeTrack!);
discord.updatePresence(playlist.activeTrack!);
updatePalette();
});
}
@ -46,17 +72,18 @@ extension ProxyPlaylistListeners on ProxyPlaylistNotifier {
String? lastScrobbled;
return audioPlayer.positionStream.listen((position) {
try {
final uid = state.activeTrack is LocalTrack
? (state.activeTrack as LocalTrack).path
: state.activeTrack?.id;
final uid = playlist.activeTrack is LocalTrack
? (playlist.activeTrack as LocalTrack).path
: playlist.activeTrack?.id;
if (state.activeTrack == null ||
if (playlist.activeTrack == null ||
lastScrobbled == uid ||
position.inSeconds < 30) {
return;
}
scrobbler.scrobble(state.activeTrack!);
scrobbler.scrobble(playlist.activeTrack!);
history.addTracks([playlist.activeTrack!]);
lastScrobbled = uid;
} catch (e, stack) {
Catcher2.reportCheckedError(e, stack);
@ -68,9 +95,9 @@ extension ProxyPlaylistListeners on ProxyPlaylistNotifier {
String lastTrack = ""; // used to prevent multiple calls to the same track
return audioPlayer.positionStream.listen((event) async {
if (event < const Duration(seconds: 3) ||
state.active == null ||
state.active == state.tracks.length - 1) return;
final nextTrack = state.tracks.elementAt(state.active! + 1);
playlist.active == null ||
playlist.active == playlist.tracks.length - 1) return;
final nextTrack = playlist.tracks.elementAt(playlist.active! + 1);
if (lastTrack == nextTrack.id || nextTrack is LocalTrack) return;

View File

@ -2,14 +2,12 @@ import 'dart:async';
import 'dart:math';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:palette_generator/palette_generator.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/shared/image/universal_image.dart';
import 'package:spotube/extensions/image.dart';
import 'package:spotube/extensions/track.dart';
import 'package:spotube/models/local_track.dart';
import 'package:spotube/provider/blacklist_provider.dart';
import 'package:spotube/provider/history/history.dart';
import 'package:spotube/provider/palette_provider.dart';
import 'package:spotube/provider/proxy_playlist/player_listeners.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart';
@ -32,6 +30,8 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist> {
ProxyPlaylist get playlist => state;
BlackListNotifier get blacklist => ref.read(blacklistProvider.notifier);
Discord get discord => ref.read(discordProvider);
PlaybackHistoryNotifier get history =>
ref.read(playbackHistoryProvider.notifier);
List<StreamSubscription> _subscriptions = [];
@ -167,28 +167,6 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist> {
discord.clear();
}
Future<void> updatePalette() async {
final palette = ref.read(paletteProvider);
if (!preferences.albumColorSync) {
if (palette != null) ref.read(paletteProvider.notifier).state = null;
return;
}
return Future.microtask(() async {
if (state.activeTrack == null) return;
final palette = await PaletteGenerator.fromImageProvider(
UniversalImage.imageProvider(
(state.activeTrack?.album?.images).asUrlString(
placeholder: ImagePlaceholder.albumArt,
),
height: 50,
width: 50,
),
);
ref.read(paletteProvider.notifier).state = palette;
});
}
@override
set state(state) {
super.state = state;

View File

@ -6,6 +6,7 @@ import 'package:path_provider/path_provider.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/settings/color_scheme_picker_dialog.dart';
import 'package:spotube/provider/palette_provider.dart';
import 'package:spotube/provider/proxy_playlist/player_listeners.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
import 'package:spotube/services/audio_player/audio_player.dart';

View File

@ -6,8 +6,7 @@ part of 'user_preferences_state.dart';
// JsonSerializableGenerator
// **************************************************************************
_$UserPreferencesImpl _$$UserPreferencesImplFromJson(
Map<String, dynamic> json) =>
_$UserPreferencesImpl _$$UserPreferencesImplFromJson(Map json) =>
_$UserPreferencesImpl(
audioQuality:
$enumDecodeNullable(_$SourceQualitiesEnumMap, json['audioQuality']) ??

View File

@ -3,7 +3,6 @@ import 'dart:io';
import 'package:catcher_2/catcher_2.dart';
import 'package:flutter/foundation.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/extensions/track.dart';
import 'package:spotube/models/local_track.dart';
import 'package:spotube/provider/server/server.dart';
import 'package:spotube/services/audio_player/custom_player.dart';

View File

@ -6,8 +6,7 @@ part of 'song_link.dart';
// JsonSerializableGenerator
// **************************************************************************
_$SongLinkImpl _$$SongLinkImplFromJson(Map<String, dynamic> json) =>
_$SongLinkImpl(
_$SongLinkImpl _$$SongLinkImplFromJson(Map json) => _$SongLinkImpl(
displayName: json['displayName'] as String,
linkId: json['linkId'] as String,
platform: json['platform'] as String,

View File

@ -6,7 +6,7 @@ part of 'source_info.dart';
// JsonSerializableGenerator
// **************************************************************************
SourceInfo _$SourceInfoFromJson(Map<String, dynamic> json) => SourceInfo(
SourceInfo _$SourceInfoFromJson(Map json) => SourceInfo(
id: json['id'] as String,
title: json['title'] as String,
artist: json['artist'] as String,

View File

@ -6,8 +6,7 @@ part of 'source_map.dart';
// JsonSerializableGenerator
// **************************************************************************
SourceQualityMap _$SourceQualityMapFromJson(Map<String, dynamic> json) =>
SourceQualityMap(
SourceQualityMap _$SourceQualityMapFromJson(Map json) => SourceQualityMap(
high: json['high'] as String,
medium: json['medium'] as String,
low: json['low'] as String,
@ -20,16 +19,18 @@ Map<String, dynamic> _$SourceQualityMapToJson(SourceQualityMap instance) =>
'low': instance.low,
};
SourceMap _$SourceMapFromJson(Map<String, dynamic> json) => SourceMap(
SourceMap _$SourceMapFromJson(Map json) => SourceMap(
weba: json['weba'] == null
? null
: SourceQualityMap.fromJson(json['weba'] as Map<String, dynamic>),
: SourceQualityMap.fromJson(
Map<String, dynamic>.from(json['weba'] as Map)),
m4a: json['m4a'] == null
? null
: SourceQualityMap.fromJson(json['m4a'] as Map<String, dynamic>),
: SourceQualityMap.fromJson(
Map<String, dynamic>.from(json['m4a'] as Map)),
);
Map<String, dynamic> _$SourceMapToJson(SourceMap instance) => <String, dynamic>{
'weba': instance.weba,
'm4a': instance.m4a,
'weba': instance.weba?.toJson(),
'm4a': instance.m4a?.toJson(),
};

View File

@ -2048,11 +2048,19 @@ packages:
dependency: "direct main"
description:
path: "."
ref: "feat/to-json"
resolved-ref: "05ace91cdfe64db23d8c62077069e7c25b3645cb"
ref: "fix/explicit-to-json"
resolved-ref: c4b37c599413ac7bfd78993e416a56105c62b634
url: "https://github.com/KRTirtho/spotify-dart.git"
source: git
version: "0.13.5"
version: "0.13.6"
sprintf:
dependency: transitive
description:
name: sprintf
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
sqflite:
dependency: transitive
description:

View File

@ -118,7 +118,7 @@ dependencies:
spotify:
git:
url: https://github.com/KRTirtho/spotify-dart.git
ref: feat/to-json
ref: fix/explicit-to-json
bonsoir: ^5.1.9
shelf: ^1.4.1
shelf_router: ^1.1.4