From 8ac30c0031899eb5f172d359ea02ed52faae2dd3 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 2 May 2025 11:36:09 +0600 Subject: [PATCH] feat(metadata-plugin): add pagination support, feed and playlist CRUD endpoints --- lib/models/metadata/feed.dart | 14 + lib/models/metadata/metadata.dart | 2 + lib/models/metadata/metadata.freezed.dart | 762 +++++++++++++++++++--- lib/models/metadata/metadata.g.dart | 80 ++- lib/models/metadata/pagination.dart | 22 + lib/models/metadata/search.dart | 67 +- lib/services/metadata/metadata.dart | 274 ++++++-- 7 files changed, 1026 insertions(+), 195 deletions(-) create mode 100644 lib/models/metadata/feed.dart create mode 100644 lib/models/metadata/pagination.dart diff --git a/lib/models/metadata/feed.dart b/lib/models/metadata/feed.dart new file mode 100644 index 00000000..3bf7c99b --- /dev/null +++ b/lib/models/metadata/feed.dart @@ -0,0 +1,14 @@ +part of 'metadata.dart'; + +@freezed +class SpotubeFeedObject with _$SpotubeFeedObject { + factory SpotubeFeedObject({ + required final String uid, + required final String name, + required final String externalUrl, + @Default([]) final List images, + }) = _SpotubeFeedObject; + + factory SpotubeFeedObject.fromJson(Map json) => + _$SpotubeFeedObjectFromJson(json); +} diff --git a/lib/models/metadata/metadata.dart b/lib/models/metadata/metadata.dart index 372aec64..0e17d796 100644 --- a/lib/models/metadata/metadata.dart +++ b/lib/models/metadata/metadata.dart @@ -7,7 +7,9 @@ part 'metadata.freezed.dart'; part 'album.dart'; part 'artist.dart'; +part 'feed.dart'; part 'image.dart'; +part 'pagination.dart'; part 'playlist.dart'; part 'search.dart'; part 'track.dart'; diff --git a/lib/models/metadata/metadata.freezed.dart b/lib/models/metadata/metadata.freezed.dart index f2364fce..c7777b76 100644 --- a/lib/models/metadata/metadata.freezed.dart +++ b/lib/models/metadata/metadata.freezed.dart @@ -545,6 +545,229 @@ abstract class _SpotubeArtistObject implements SpotubeArtistObject { throw _privateConstructorUsedError; } +SpotubeFeedObject _$SpotubeFeedObjectFromJson(Map json) { + return _SpotubeFeedObject.fromJson(json); +} + +/// @nodoc +mixin _$SpotubeFeedObject { + String get uid => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + String get externalUrl => throw _privateConstructorUsedError; + List get images => throw _privateConstructorUsedError; + + /// Serializes this SpotubeFeedObject to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SpotubeFeedObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SpotubeFeedObjectCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SpotubeFeedObjectCopyWith<$Res> { + factory $SpotubeFeedObjectCopyWith( + SpotubeFeedObject value, $Res Function(SpotubeFeedObject) then) = + _$SpotubeFeedObjectCopyWithImpl<$Res, SpotubeFeedObject>; + @useResult + $Res call( + {String uid, + String name, + String externalUrl, + List images}); +} + +/// @nodoc +class _$SpotubeFeedObjectCopyWithImpl<$Res, $Val extends SpotubeFeedObject> + implements $SpotubeFeedObjectCopyWith<$Res> { + _$SpotubeFeedObjectCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SpotubeFeedObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? uid = null, + Object? name = null, + Object? externalUrl = null, + Object? images = null, + }) { + return _then(_value.copyWith( + uid: null == uid + ? _value.uid + : uid // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + externalUrl: null == externalUrl + ? _value.externalUrl + : externalUrl // ignore: cast_nullable_to_non_nullable + as String, + images: null == images + ? _value.images + : images // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SpotubeFeedObjectImplCopyWith<$Res> + implements $SpotubeFeedObjectCopyWith<$Res> { + factory _$$SpotubeFeedObjectImplCopyWith(_$SpotubeFeedObjectImpl value, + $Res Function(_$SpotubeFeedObjectImpl) then) = + __$$SpotubeFeedObjectImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String uid, + String name, + String externalUrl, + List images}); +} + +/// @nodoc +class __$$SpotubeFeedObjectImplCopyWithImpl<$Res> + extends _$SpotubeFeedObjectCopyWithImpl<$Res, _$SpotubeFeedObjectImpl> + implements _$$SpotubeFeedObjectImplCopyWith<$Res> { + __$$SpotubeFeedObjectImplCopyWithImpl(_$SpotubeFeedObjectImpl _value, + $Res Function(_$SpotubeFeedObjectImpl) _then) + : super(_value, _then); + + /// Create a copy of SpotubeFeedObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? uid = null, + Object? name = null, + Object? externalUrl = null, + Object? images = null, + }) { + return _then(_$SpotubeFeedObjectImpl( + uid: null == uid + ? _value.uid + : uid // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + externalUrl: null == externalUrl + ? _value.externalUrl + : externalUrl // ignore: cast_nullable_to_non_nullable + as String, + images: null == images + ? _value._images + : images // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SpotubeFeedObjectImpl implements _SpotubeFeedObject { + _$SpotubeFeedObjectImpl( + {required this.uid, + required this.name, + required this.externalUrl, + final List images = const []}) + : _images = images; + + factory _$SpotubeFeedObjectImpl.fromJson(Map json) => + _$$SpotubeFeedObjectImplFromJson(json); + + @override + final String uid; + @override + final String name; + @override + final String externalUrl; + final List _images; + @override + @JsonKey() + List get images { + if (_images is EqualUnmodifiableListView) return _images; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_images); + } + + @override + String toString() { + return 'SpotubeFeedObject(uid: $uid, name: $name, externalUrl: $externalUrl, images: $images)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SpotubeFeedObjectImpl && + (identical(other.uid, uid) || other.uid == uid) && + (identical(other.name, name) || other.name == name) && + (identical(other.externalUrl, externalUrl) || + other.externalUrl == externalUrl) && + const DeepCollectionEquality().equals(other._images, _images)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, uid, name, externalUrl, + const DeepCollectionEquality().hash(_images)); + + /// Create a copy of SpotubeFeedObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SpotubeFeedObjectImplCopyWith<_$SpotubeFeedObjectImpl> get copyWith => + __$$SpotubeFeedObjectImplCopyWithImpl<_$SpotubeFeedObjectImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$SpotubeFeedObjectImplToJson( + this, + ); + } +} + +abstract class _SpotubeFeedObject implements SpotubeFeedObject { + factory _SpotubeFeedObject( + {required final String uid, + required final String name, + required final String externalUrl, + final List images}) = _$SpotubeFeedObjectImpl; + + factory _SpotubeFeedObject.fromJson(Map json) = + _$SpotubeFeedObjectImpl.fromJson; + + @override + String get uid; + @override + String get name; + @override + String get externalUrl; + @override + List get images; + + /// Create a copy of SpotubeFeedObject + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SpotubeFeedObjectImplCopyWith<_$SpotubeFeedObjectImpl> get copyWith => + throw _privateConstructorUsedError; +} + SpotubeImageObject _$SpotubeImageObjectFromJson(Map json) { return _SpotubeImageObject.fromJson(json); } @@ -731,6 +954,261 @@ abstract class _SpotubeImageObject implements SpotubeImageObject { throw _privateConstructorUsedError; } +SpotubePaginationResponseObject _$SpotubePaginationResponseObjectFromJson( + Map json, T Function(Object?) fromJsonT) { + return _SpotubePaginationResponseObject.fromJson(json, fromJsonT); +} + +/// @nodoc +mixin _$SpotubePaginationResponseObject { + int get total => throw _privateConstructorUsedError; + String? get nextCursor => throw _privateConstructorUsedError; + String get limit => throw _privateConstructorUsedError; + bool get hasMore => throw _privateConstructorUsedError; + List get items => throw _privateConstructorUsedError; + + /// Serializes this SpotubePaginationResponseObject to a JSON map. + Map toJson(Object? Function(T) toJsonT) => + throw _privateConstructorUsedError; + + /// Create a copy of SpotubePaginationResponseObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SpotubePaginationResponseObjectCopyWith> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SpotubePaginationResponseObjectCopyWith { + factory $SpotubePaginationResponseObjectCopyWith( + SpotubePaginationResponseObject value, + $Res Function(SpotubePaginationResponseObject) then) = + _$SpotubePaginationResponseObjectCopyWithImpl>; + @useResult + $Res call( + {int total, + String? nextCursor, + String limit, + bool hasMore, + List items}); +} + +/// @nodoc +class _$SpotubePaginationResponseObjectCopyWithImpl> + implements $SpotubePaginationResponseObjectCopyWith { + _$SpotubePaginationResponseObjectCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SpotubePaginationResponseObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? total = null, + Object? nextCursor = freezed, + Object? limit = null, + Object? hasMore = null, + Object? items = null, + }) { + return _then(_value.copyWith( + total: null == total + ? _value.total + : total // ignore: cast_nullable_to_non_nullable + as int, + nextCursor: freezed == nextCursor + ? _value.nextCursor + : nextCursor // ignore: cast_nullable_to_non_nullable + as String?, + limit: null == limit + ? _value.limit + : limit // ignore: cast_nullable_to_non_nullable + as String, + hasMore: null == hasMore + ? _value.hasMore + : hasMore // ignore: cast_nullable_to_non_nullable + as bool, + items: null == items + ? _value.items + : items // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SpotubePaginationResponseObjectImplCopyWith + implements $SpotubePaginationResponseObjectCopyWith { + factory _$$SpotubePaginationResponseObjectImplCopyWith( + _$SpotubePaginationResponseObjectImpl value, + $Res Function(_$SpotubePaginationResponseObjectImpl) then) = + __$$SpotubePaginationResponseObjectImplCopyWithImpl; + @override + @useResult + $Res call( + {int total, + String? nextCursor, + String limit, + bool hasMore, + List items}); +} + +/// @nodoc +class __$$SpotubePaginationResponseObjectImplCopyWithImpl + extends _$SpotubePaginationResponseObjectCopyWithImpl> + implements _$$SpotubePaginationResponseObjectImplCopyWith { + __$$SpotubePaginationResponseObjectImplCopyWithImpl( + _$SpotubePaginationResponseObjectImpl _value, + $Res Function(_$SpotubePaginationResponseObjectImpl) _then) + : super(_value, _then); + + /// Create a copy of SpotubePaginationResponseObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? total = null, + Object? nextCursor = freezed, + Object? limit = null, + Object? hasMore = null, + Object? items = null, + }) { + return _then(_$SpotubePaginationResponseObjectImpl( + total: null == total + ? _value.total + : total // ignore: cast_nullable_to_non_nullable + as int, + nextCursor: freezed == nextCursor + ? _value.nextCursor + : nextCursor // ignore: cast_nullable_to_non_nullable + as String?, + limit: null == limit + ? _value.limit + : limit // ignore: cast_nullable_to_non_nullable + as String, + hasMore: null == hasMore + ? _value.hasMore + : hasMore // ignore: cast_nullable_to_non_nullable + as bool, + items: null == items + ? _value._items + : items // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable(genericArgumentFactories: true) +class _$SpotubePaginationResponseObjectImpl + implements _SpotubePaginationResponseObject { + _$SpotubePaginationResponseObjectImpl( + {required this.total, + required this.nextCursor, + required this.limit, + required this.hasMore, + required final List items}) + : _items = items; + + factory _$SpotubePaginationResponseObjectImpl.fromJson( + Map json, T Function(Object?) fromJsonT) => + _$$SpotubePaginationResponseObjectImplFromJson(json, fromJsonT); + + @override + final int total; + @override + final String? nextCursor; + @override + final String limit; + @override + final bool hasMore; + final List _items; + @override + List get items { + if (_items is EqualUnmodifiableListView) return _items; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_items); + } + + @override + String toString() { + return 'SpotubePaginationResponseObject<$T>(total: $total, nextCursor: $nextCursor, limit: $limit, hasMore: $hasMore, items: $items)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SpotubePaginationResponseObjectImpl && + (identical(other.total, total) || other.total == total) && + (identical(other.nextCursor, nextCursor) || + other.nextCursor == nextCursor) && + (identical(other.limit, limit) || other.limit == limit) && + (identical(other.hasMore, hasMore) || other.hasMore == hasMore) && + const DeepCollectionEquality().equals(other._items, _items)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, total, nextCursor, limit, + hasMore, const DeepCollectionEquality().hash(_items)); + + /// Create a copy of SpotubePaginationResponseObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SpotubePaginationResponseObjectImplCopyWith> + get copyWith => __$$SpotubePaginationResponseObjectImplCopyWithImpl>(this, _$identity); + + @override + Map toJson(Object? Function(T) toJsonT) { + return _$$SpotubePaginationResponseObjectImplToJson(this, toJsonT); + } +} + +abstract class _SpotubePaginationResponseObject + implements SpotubePaginationResponseObject { + factory _SpotubePaginationResponseObject( + {required final int total, + required final String? nextCursor, + required final String limit, + required final bool hasMore, + required final List items}) = _$SpotubePaginationResponseObjectImpl; + + factory _SpotubePaginationResponseObject.fromJson( + Map json, T Function(Object?) fromJsonT) = + _$SpotubePaginationResponseObjectImpl.fromJson; + + @override + int get total; + @override + String? get nextCursor; + @override + String get limit; + @override + bool get hasMore; + @override + List get items; + + /// Create a copy of SpotubePaginationResponseObject + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SpotubePaginationResponseObjectImplCopyWith> + get copyWith => throw _privateConstructorUsedError; +} + SpotubePlaylistObject _$SpotubePlaylistObjectFromJson( Map json) { return _SpotubePlaylistObject.fromJson(json); @@ -1059,10 +1537,17 @@ SpotubeSearchResponseObject _$SpotubeSearchResponseObjectFromJson( /// @nodoc mixin _$SpotubeSearchResponseObject { - List get tracks => throw _privateConstructorUsedError; - List get albums => throw _privateConstructorUsedError; - List get artists => throw _privateConstructorUsedError; - List get playlists => + @JsonKey(fromJson: _paginationTracksFromJson, toJson: _paginationToJson) + SpotubePaginationResponseObject? get tracks => + throw _privateConstructorUsedError; + @JsonKey(fromJson: _paginationAlbumsFromJson, toJson: _paginationToJson) + SpotubePaginationResponseObject? get albums => + throw _privateConstructorUsedError; + @JsonKey(fromJson: _paginationArtistsFromJson, toJson: _paginationToJson) + SpotubePaginationResponseObject? get artists => + throw _privateConstructorUsedError; + @JsonKey(fromJson: _paginationPlaylistsFromJson, toJson: _paginationToJson) + SpotubePaginationResponseObject? get playlists => throw _privateConstructorUsedError; /// Serializes this SpotubeSearchResponseObject to a JSON map. @@ -1084,10 +1569,24 @@ abstract class $SpotubeSearchResponseObjectCopyWith<$Res> { SpotubeSearchResponseObject>; @useResult $Res call( - {List tracks, - List albums, - List artists, - List playlists}); + {@JsonKey(fromJson: _paginationTracksFromJson, toJson: _paginationToJson) + SpotubePaginationResponseObject? tracks, + @JsonKey(fromJson: _paginationAlbumsFromJson, toJson: _paginationToJson) + SpotubePaginationResponseObject? albums, + @JsonKey(fromJson: _paginationArtistsFromJson, toJson: _paginationToJson) + SpotubePaginationResponseObject? artists, + @JsonKey( + fromJson: _paginationPlaylistsFromJson, toJson: _paginationToJson) + SpotubePaginationResponseObject? playlists}); + + $SpotubePaginationResponseObjectCopyWith? + get tracks; + $SpotubePaginationResponseObjectCopyWith? + get albums; + $SpotubePaginationResponseObjectCopyWith? + get artists; + $SpotubePaginationResponseObjectCopyWith? + get playlists; } /// @nodoc @@ -1106,30 +1605,94 @@ class _$SpotubeSearchResponseObjectCopyWithImpl<$Res, @pragma('vm:prefer-inline') @override $Res call({ - Object? tracks = null, - Object? albums = null, - Object? artists = null, - Object? playlists = null, + Object? tracks = freezed, + Object? albums = freezed, + Object? artists = freezed, + Object? playlists = freezed, }) { return _then(_value.copyWith( - tracks: null == tracks + tracks: freezed == tracks ? _value.tracks : tracks // ignore: cast_nullable_to_non_nullable - as List, - albums: null == albums + as SpotubePaginationResponseObject?, + albums: freezed == albums ? _value.albums : albums // ignore: cast_nullable_to_non_nullable - as List, - artists: null == artists + as SpotubePaginationResponseObject?, + artists: freezed == artists ? _value.artists : artists // ignore: cast_nullable_to_non_nullable - as List, - playlists: null == playlists + as SpotubePaginationResponseObject?, + playlists: freezed == playlists ? _value.playlists : playlists // ignore: cast_nullable_to_non_nullable - as List, + as SpotubePaginationResponseObject?, ) as $Val); } + + /// Create a copy of SpotubeSearchResponseObject + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $SpotubePaginationResponseObjectCopyWith? + get tracks { + if (_value.tracks == null) { + return null; + } + + return $SpotubePaginationResponseObjectCopyWith( + _value.tracks!, (value) { + return _then(_value.copyWith(tracks: value) as $Val); + }); + } + + /// Create a copy of SpotubeSearchResponseObject + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $SpotubePaginationResponseObjectCopyWith? + get albums { + if (_value.albums == null) { + return null; + } + + return $SpotubePaginationResponseObjectCopyWith( + _value.albums!, (value) { + return _then(_value.copyWith(albums: value) as $Val); + }); + } + + /// Create a copy of SpotubeSearchResponseObject + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $SpotubePaginationResponseObjectCopyWith? + get artists { + if (_value.artists == null) { + return null; + } + + return $SpotubePaginationResponseObjectCopyWith( + _value.artists!, (value) { + return _then(_value.copyWith(artists: value) as $Val); + }); + } + + /// Create a copy of SpotubeSearchResponseObject + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $SpotubePaginationResponseObjectCopyWith? + get playlists { + if (_value.playlists == null) { + return null; + } + + return $SpotubePaginationResponseObjectCopyWith(_value.playlists!, (value) { + return _then(_value.copyWith(playlists: value) as $Val); + }); + } } /// @nodoc @@ -1142,10 +1705,28 @@ abstract class _$$SpotubeSearchResponseObjectImplCopyWith<$Res> @override @useResult $Res call( - {List tracks, - List albums, - List artists, - List playlists}); + {@JsonKey(fromJson: _paginationTracksFromJson, toJson: _paginationToJson) + SpotubePaginationResponseObject? tracks, + @JsonKey(fromJson: _paginationAlbumsFromJson, toJson: _paginationToJson) + SpotubePaginationResponseObject? albums, + @JsonKey(fromJson: _paginationArtistsFromJson, toJson: _paginationToJson) + SpotubePaginationResponseObject? artists, + @JsonKey( + fromJson: _paginationPlaylistsFromJson, toJson: _paginationToJson) + SpotubePaginationResponseObject? playlists}); + + @override + $SpotubePaginationResponseObjectCopyWith? + get tracks; + @override + $SpotubePaginationResponseObjectCopyWith? + get albums; + @override + $SpotubePaginationResponseObjectCopyWith? + get artists; + @override + $SpotubePaginationResponseObjectCopyWith? + get playlists; } /// @nodoc @@ -1163,28 +1744,28 @@ class __$$SpotubeSearchResponseObjectImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? tracks = null, - Object? albums = null, - Object? artists = null, - Object? playlists = null, + Object? tracks = freezed, + Object? albums = freezed, + Object? artists = freezed, + Object? playlists = freezed, }) { return _then(_$SpotubeSearchResponseObjectImpl( - tracks: null == tracks - ? _value._tracks + tracks: freezed == tracks + ? _value.tracks : tracks // ignore: cast_nullable_to_non_nullable - as List, - albums: null == albums - ? _value._albums + as SpotubePaginationResponseObject?, + albums: freezed == albums + ? _value.albums : albums // ignore: cast_nullable_to_non_nullable - as List, - artists: null == artists - ? _value._artists + as SpotubePaginationResponseObject?, + artists: freezed == artists + ? _value.artists : artists // ignore: cast_nullable_to_non_nullable - as List, - playlists: null == playlists - ? _value._playlists + as SpotubePaginationResponseObject?, + playlists: freezed == playlists + ? _value.playlists : playlists // ignore: cast_nullable_to_non_nullable - as List, + as SpotubePaginationResponseObject?, )); } } @@ -1194,54 +1775,32 @@ class __$$SpotubeSearchResponseObjectImplCopyWithImpl<$Res> class _$SpotubeSearchResponseObjectImpl implements _SpotubeSearchResponseObject { _$SpotubeSearchResponseObjectImpl( - {final List tracks = const [], - final List albums = const [], - final List artists = const [], - final List playlists = const []}) - : _tracks = tracks, - _albums = albums, - _artists = artists, - _playlists = playlists; + {@JsonKey(fromJson: _paginationTracksFromJson, toJson: _paginationToJson) + this.tracks, + @JsonKey(fromJson: _paginationAlbumsFromJson, toJson: _paginationToJson) + this.albums, + @JsonKey(fromJson: _paginationArtistsFromJson, toJson: _paginationToJson) + this.artists, + @JsonKey( + fromJson: _paginationPlaylistsFromJson, toJson: _paginationToJson) + this.playlists}); factory _$SpotubeSearchResponseObjectImpl.fromJson( Map json) => _$$SpotubeSearchResponseObjectImplFromJson(json); - final List _tracks; @override - @JsonKey() - List get tracks { - if (_tracks is EqualUnmodifiableListView) return _tracks; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_tracks); - } - - final List _albums; + @JsonKey(fromJson: _paginationTracksFromJson, toJson: _paginationToJson) + final SpotubePaginationResponseObject? tracks; @override - @JsonKey() - List get albums { - if (_albums is EqualUnmodifiableListView) return _albums; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_albums); - } - - final List _artists; + @JsonKey(fromJson: _paginationAlbumsFromJson, toJson: _paginationToJson) + final SpotubePaginationResponseObject? albums; @override - @JsonKey() - List get artists { - if (_artists is EqualUnmodifiableListView) return _artists; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_artists); - } - - final List _playlists; + @JsonKey(fromJson: _paginationArtistsFromJson, toJson: _paginationToJson) + final SpotubePaginationResponseObject? artists; @override - @JsonKey() - List get playlists { - if (_playlists is EqualUnmodifiableListView) return _playlists; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_playlists); - } + @JsonKey(fromJson: _paginationPlaylistsFromJson, toJson: _paginationToJson) + final SpotubePaginationResponseObject? playlists; @override String toString() { @@ -1253,21 +1812,17 @@ class _$SpotubeSearchResponseObjectImpl return identical(this, other) || (other.runtimeType == runtimeType && other is _$SpotubeSearchResponseObjectImpl && - const DeepCollectionEquality().equals(other._tracks, _tracks) && - const DeepCollectionEquality().equals(other._albums, _albums) && - const DeepCollectionEquality().equals(other._artists, _artists) && - const DeepCollectionEquality() - .equals(other._playlists, _playlists)); + (identical(other.tracks, tracks) || other.tracks == tracks) && + (identical(other.albums, albums) || other.albums == albums) && + (identical(other.artists, artists) || other.artists == artists) && + (identical(other.playlists, playlists) || + other.playlists == playlists)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( - runtimeType, - const DeepCollectionEquality().hash(_tracks), - const DeepCollectionEquality().hash(_albums), - const DeepCollectionEquality().hash(_artists), - const DeepCollectionEquality().hash(_playlists)); + int get hashCode => + Object.hash(runtimeType, tracks, albums, artists, playlists); /// Create a copy of SpotubeSearchResponseObject /// with the given fields replaced by the non-null parameter values. @@ -1289,23 +1844,32 @@ class _$SpotubeSearchResponseObjectImpl abstract class _SpotubeSearchResponseObject implements SpotubeSearchResponseObject { factory _SpotubeSearchResponseObject( - {final List tracks, - final List albums, - final List artists, - final List playlists}) = - _$SpotubeSearchResponseObjectImpl; + {@JsonKey(fromJson: _paginationTracksFromJson, toJson: _paginationToJson) + final SpotubePaginationResponseObject? tracks, + @JsonKey(fromJson: _paginationAlbumsFromJson, toJson: _paginationToJson) + final SpotubePaginationResponseObject? albums, + @JsonKey(fromJson: _paginationArtistsFromJson, toJson: _paginationToJson) + final SpotubePaginationResponseObject? artists, + @JsonKey( + fromJson: _paginationPlaylistsFromJson, toJson: _paginationToJson) + final SpotubePaginationResponseObject? + playlists}) = _$SpotubeSearchResponseObjectImpl; factory _SpotubeSearchResponseObject.fromJson(Map json) = _$SpotubeSearchResponseObjectImpl.fromJson; @override - List get tracks; + @JsonKey(fromJson: _paginationTracksFromJson, toJson: _paginationToJson) + SpotubePaginationResponseObject? get tracks; @override - List get albums; + @JsonKey(fromJson: _paginationAlbumsFromJson, toJson: _paginationToJson) + SpotubePaginationResponseObject? get albums; @override - List get artists; + @JsonKey(fromJson: _paginationArtistsFromJson, toJson: _paginationToJson) + SpotubePaginationResponseObject? get artists; @override - List get playlists; + @JsonKey(fromJson: _paginationPlaylistsFromJson, toJson: _paginationToJson) + SpotubePaginationResponseObject? get playlists; /// Create a copy of SpotubeSearchResponseObject /// with the given fields replaced by the non-null parameter values. diff --git a/lib/models/metadata/metadata.g.dart b/lib/models/metadata/metadata.g.dart index ee386b19..5e76a653 100644 --- a/lib/models/metadata/metadata.g.dart +++ b/lib/models/metadata/metadata.g.dart @@ -60,6 +60,27 @@ Map _$$SpotubeArtistObjectImplToJson( 'externalUrl': instance.externalUrl, }; +_$SpotubeFeedObjectImpl _$$SpotubeFeedObjectImplFromJson(Map json) => + _$SpotubeFeedObjectImpl( + uid: json['uid'] as String, + name: json['name'] as String, + externalUrl: json['externalUrl'] as String, + images: (json['images'] as List?) + ?.map((e) => SpotubeImageObject.fromJson( + Map.from(e as Map))) + .toList() ?? + const [], + ); + +Map _$$SpotubeFeedObjectImplToJson( + _$SpotubeFeedObjectImpl instance) => + { + 'uid': instance.uid, + 'name': instance.name, + 'externalUrl': instance.externalUrl, + 'images': instance.images.map((e) => e.toJson()).toList(), + }; + _$SpotubeImageObjectImpl _$$SpotubeImageObjectImplFromJson(Map json) => _$SpotubeImageObjectImpl( url: json['url'] as String, @@ -75,6 +96,31 @@ Map _$$SpotubeImageObjectImplToJson( 'height': instance.height, }; +_$SpotubePaginationResponseObjectImpl + _$$SpotubePaginationResponseObjectImplFromJson( + Map json, + T Function(Object? json) fromJsonT, +) => + _$SpotubePaginationResponseObjectImpl( + total: (json['total'] as num).toInt(), + nextCursor: json['nextCursor'] as String?, + limit: json['limit'] as String, + hasMore: json['hasMore'] as bool, + items: (json['items'] as List).map(fromJsonT).toList(), + ); + +Map _$$SpotubePaginationResponseObjectImplToJson( + _$SpotubePaginationResponseObjectImpl instance, + Object? Function(T value) toJsonT, +) => + { + 'total': instance.total, + 'nextCursor': instance.nextCursor, + 'limit': instance.limit, + 'hasMore': instance.hasMore, + 'items': instance.items.map(toJsonT).toList(), + }; + _$SpotubePlaylistObjectImpl _$$SpotubePlaylistObjectImplFromJson(Map json) => _$SpotubePlaylistObjectImpl( uid: json['uid'] as String, @@ -110,35 +156,21 @@ Map _$$SpotubePlaylistObjectImplToJson( _$SpotubeSearchResponseObjectImpl _$$SpotubeSearchResponseObjectImplFromJson( Map json) => _$SpotubeSearchResponseObjectImpl( - tracks: (json['tracks'] as List?) - ?.map((e) => SpotubeTrackObject.fromJson( - Map.from(e as Map))) - .toList() ?? - const [], - albums: (json['albums'] as List?) - ?.map((e) => SpotubeAlbumObject.fromJson( - Map.from(e as Map))) - .toList() ?? - const [], - artists: (json['artists'] as List?) - ?.map((e) => SpotubeArtistObject.fromJson( - Map.from(e as Map))) - .toList() ?? - const [], - playlists: (json['playlists'] as List?) - ?.map((e) => SpotubePlaylistObject.fromJson( - Map.from(e as Map))) - .toList() ?? - const [], + tracks: _paginationTracksFromJson(json['tracks'] as Map), + albums: _paginationAlbumsFromJson(json['albums'] as Map), + artists: + _paginationArtistsFromJson(json['artists'] as Map), + playlists: _paginationPlaylistsFromJson( + json['playlists'] as Map), ); Map _$$SpotubeSearchResponseObjectImplToJson( _$SpotubeSearchResponseObjectImpl instance) => { - 'tracks': instance.tracks.map((e) => e.toJson()).toList(), - 'albums': instance.albums.map((e) => e.toJson()).toList(), - 'artists': instance.artists.map((e) => e.toJson()).toList(), - 'playlists': instance.playlists.map((e) => e.toJson()).toList(), + 'tracks': _paginationToJson(instance.tracks), + 'albums': _paginationToJson(instance.albums), + 'artists': _paginationToJson(instance.artists), + 'playlists': _paginationToJson(instance.playlists), }; _$SpotubeTrackObjectImpl _$$SpotubeTrackObjectImplFromJson(Map json) => diff --git a/lib/models/metadata/pagination.dart b/lib/models/metadata/pagination.dart new file mode 100644 index 00000000..6fca7128 --- /dev/null +++ b/lib/models/metadata/pagination.dart @@ -0,0 +1,22 @@ +part of 'metadata.dart'; + +@Freezed(genericArgumentFactories: true) +class SpotubePaginationResponseObject + with _$SpotubePaginationResponseObject { + factory SpotubePaginationResponseObject({ + required int total, + required String? nextCursor, + required String limit, + required bool hasMore, + required List items, + }) = _SpotubePaginationResponseObject; + + factory SpotubePaginationResponseObject.fromJson( + Map json, + T Function(Map json) fromJsonT, + ) => + _$SpotubePaginationResponseObjectFromJson( + json, + (json) => fromJsonT(json as Map), + ); +} diff --git a/lib/models/metadata/search.dart b/lib/models/metadata/search.dart index 011238f7..5a379fa1 100644 --- a/lib/models/metadata/search.dart +++ b/lib/models/metadata/search.dart @@ -1,12 +1,71 @@ part of 'metadata.dart'; +SpotubePaginationResponseObject _paginationTracksFromJson( + Map json, +) { + return SpotubePaginationResponseObject.fromJson( + json, + (json) => SpotubeTrackObject.fromJson(json), + ); +} + +SpotubePaginationResponseObject _paginationAlbumsFromJson( + Map json, +) { + return SpotubePaginationResponseObject.fromJson( + json, + (json) => SpotubeAlbumObject.fromJson(json), + ); +} + +SpotubePaginationResponseObject _paginationArtistsFromJson( + Map json, +) { + return SpotubePaginationResponseObject.fromJson( + json, + (json) => SpotubeArtistObject.fromJson(json), + ); +} + +SpotubePaginationResponseObject + _paginationPlaylistsFromJson( + Map json, +) { + return SpotubePaginationResponseObject.fromJson( + json, + (json) => SpotubePlaylistObject.fromJson(json), + ); +} + +Map? _paginationToJson( + SpotubePaginationResponseObject? instance, +) { + return instance?.toJson((item) => item.toJson()); +} + @freezed class SpotubeSearchResponseObject with _$SpotubeSearchResponseObject { factory SpotubeSearchResponseObject({ - @Default([]) final List tracks, - @Default([]) final List albums, - @Default([]) final List artists, - @Default([]) final List playlists, + @JsonKey( + fromJson: _paginationTracksFromJson, + toJson: _paginationToJson, + ) + final SpotubePaginationResponseObject? tracks, + @JsonKey( + fromJson: _paginationAlbumsFromJson, + toJson: _paginationToJson, + ) + final SpotubePaginationResponseObject? albums, + @JsonKey( + fromJson: _paginationArtistsFromJson, + toJson: _paginationToJson, + ) + final SpotubePaginationResponseObject? artists, + @JsonKey( + fromJson: _paginationPlaylistsFromJson, + toJson: _paginationToJson, + ) + final SpotubePaginationResponseObject? playlists, }) = _SpotubeSearchResponseObject; factory SpotubeSearchResponseObject.fromJson(Map json) => diff --git a/lib/services/metadata/metadata.dart b/lib/services/metadata/metadata.dart index 138af397..611f4720 100644 --- a/lib/services/metadata/metadata.dart +++ b/lib/services/metadata/metadata.dart @@ -7,8 +7,7 @@ import 'package:flutter_js/flutter_js.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/services/logger/logger.dart'; -const int defaultMetadataLimit = 20; -const int defaultMetadataOffset = 0; +const defaultMetadataLimit = "20"; /// Signature for metadata and related methods that will return Spotube native /// objects e.g. SpotubeTrack, SpotubePlaylist, etc. @@ -97,73 +96,91 @@ class MetadataApiSignature { return SpotubeTrackObject.fromJson(result); } - Future> listTracks({ + Future> listTracks({ List? ids, - int limit = defaultMetadataLimit, - int offset = defaultMetadataOffset, + String limit = defaultMetadataLimit, + String? cursor, }) async { final result = await invoke( "metadataApi.listTracks", [ ids, limit, - offset, + cursor, ], ); - return result.map(SpotubeTrackObject.fromJson).toList(); + return SpotubePaginationResponseObject.fromJson( + result, + SpotubeTrackObject.fromJson, + ); } - Future> listTracksByAlbum( + Future> listTracksByAlbum( String albumId, { - int limit = defaultMetadataLimit, - int offset = defaultMetadataOffset, + String limit = defaultMetadataLimit, + String? cursor, }) async { final res = await invoke( "metadataApi.listTracksByAlbum", - [albumId, limit, offset], + [albumId, limit, cursor], ); - return res.map(SpotubeTrackObject.fromJson).toList(); + return SpotubePaginationResponseObject.fromJson( + res, + SpotubeTrackObject.fromJson, + ); } - Future> listTopTracksByArtist( + Future> + listTopTracksByArtist( String artistId, { - int limit = defaultMetadataLimit, - int offset = defaultMetadataOffset, + String limit = defaultMetadataLimit, + String? cursor, }) async { final res = await invoke( "metadataApi.listTopTracksByArtist", - [artistId, limit, offset], + [artistId, limit, cursor], ); - return res.map(SpotubeTrackObject.fromJson).toList(); + return SpotubePaginationResponseObject.fromJson( + res, + SpotubeTrackObject.fromJson, + ); } - Future> listTracksByPlaylist( + Future> + listTracksByPlaylist( String playlistId, { - int limit = defaultMetadataLimit, - int offset = defaultMetadataOffset, + String limit = defaultMetadataLimit, + String? cursor, }) async { final res = await invoke( "metadataApi.listTracksByPlaylist", - [playlistId, limit, offset], + [playlistId, limit, cursor], ); - return res.map(SpotubeTrackObject.fromJson).toList(); + return SpotubePaginationResponseObject.fromJson( + res, + SpotubeTrackObject.fromJson, + ); } - Future> listUserSavedTracks( + Future> + listUserSavedTracks( String userId, { - int limit = defaultMetadataLimit, - int offset = defaultMetadataOffset, + String limit = defaultMetadataLimit, + String? cursor, }) async { final res = await invoke( "metadataApi.listUserSavedTracks", - [userId, limit, offset], + [userId, limit, cursor], ); - return res.map(SpotubeTrackObject.fromJson).toList(); + return SpotubePaginationResponseObject.fromJson( + res, + SpotubeTrackObject.fromJson, + ); } // ----- Album ------ @@ -173,43 +190,54 @@ class MetadataApiSignature { return SpotubeAlbumObject.fromJson(res); } - Future> listAlbums({ + Future> listAlbums({ List? ids, - int limit = defaultMetadataLimit, - int offset = defaultMetadataOffset, + String limit = defaultMetadataLimit, + String? cursor, }) async { final res = await invoke( "metadataApi.listAlbums", - [ids, limit, offset], + [ids, limit, cursor], ); - return res.map(SpotubeAlbumObject.fromJson).toList(); + return SpotubePaginationResponseObject.fromJson( + res, + SpotubeAlbumObject.fromJson, + ); } - Future> listAlbumsByArtist( + Future> + listAlbumsByArtist( String artistId, { - int limit = defaultMetadataLimit, - int offset = defaultMetadataOffset, + String limit = defaultMetadataLimit, + String? cursor, }) async { final res = await invoke( "metadataApi.listAlbumsByArtist", - [artistId, limit, offset], + [artistId, limit, cursor], ); - return res.map(SpotubeAlbumObject.fromJson).toList(); + return SpotubePaginationResponseObject.fromJson( + res, + SpotubeAlbumObject.fromJson, + ); } - Future> listUserSavedAlbums( + Future> + listUserSavedAlbums( String userId, { - int limit = defaultMetadataLimit, - int offset = defaultMetadataOffset, + String limit = defaultMetadataLimit, + String? cursor, }) async { final res = await invoke( "metadataApi.listUserSavedAlbums", - [userId, limit, offset], + [userId, limit, cursor], ); - return res.map(SpotubeAlbumObject.fromJson).toList(); + return SpotubePaginationResponseObject.fromJson( + res, + SpotubeAlbumObject.fromJson, + ); } // ----- Playlist ------ @@ -219,30 +247,114 @@ class MetadataApiSignature { return SpotubePlaylistObject.fromJson(res); } - Future> listPlaylists({ - List? ids, - int limit = defaultMetadataLimit, - int offset = defaultMetadataOffset, + Future> + listFeedPlaylists( + String feedId, { + String limit = defaultMetadataLimit, + String? cursor, }) async { final res = await invoke( - "metadataApi.listPlaylists", - [ids, limit, offset], + "metadataApi.listFeedPlaylists", + [feedId, limit, cursor], ); - return res.map(SpotubePlaylistObject.fromJson).toList(); + return SpotubePaginationResponseObject.fromJson( + res, + SpotubePlaylistObject.fromJson, + ); } - Future> listUserSavedPlaylists( + Future> + listUserSavedPlaylists( String userId, { - int limit = defaultMetadataLimit, - int offset = defaultMetadataOffset, + String limit = defaultMetadataLimit, + String? cursor, }) async { final res = await invoke( "metadataApi.listUserSavedPlaylists", - [userId, limit, offset], + [userId, limit, cursor], ); - return res.map(SpotubePlaylistObject.fromJson).toList(); + return SpotubePaginationResponseObject.fromJson( + res, + SpotubePlaylistObject.fromJson, + ); + } + + Future createPlaylist( + String userId, + String name, { + String? description, + bool? public, + bool? collaborative, + String? imageBase64, + }) async { + final res = await invoke( + "metadataApi.createPlaylist", + [ + userId, + name, + description, + public, + collaborative, + imageBase64, + ], + ); + + return SpotubePlaylistObject.fromJson(res); + } + + Future updatePlaylist( + String playlistId, { + String? name, + String? description, + bool? public, + bool? collaborative, + String? imageBase64, + }) async { + await invoke( + "metadataApi.updatePlaylist", + [ + playlistId, + name, + description, + public, + collaborative, + imageBase64, + ], + ); + } + + Future deletePlaylist(String userId, String playlistId) async { + await unsavePlaylist(userId, playlistId); + } + + Future addTracksToPlaylist( + String playlistId, + List trackIds, { + int? position, + }) async { + await invoke( + "metadataApi.addTracksToPlaylist", + [ + playlistId, + trackIds, + position, + ], + ); + } + + Future removeTracksFromPlaylist( + String playlistId, + List trackIds, + ) async { + await invoke( + "metadataApi.removeTracksFromPlaylist", + [ + playlistId, + trackIds, + ], + ); } // ----- Artist ------ @@ -252,44 +364,70 @@ class MetadataApiSignature { return SpotubeArtistObject.fromJson(res); } - Future> listArtists({ + Future> listArtists({ List? ids, - int limit = defaultMetadataLimit, - int offset = defaultMetadataOffset, + String limit = defaultMetadataLimit, + String? cursor, }) async { final res = await invoke( "metadataApi.listArtists", - [ids, limit, offset], + [ids, limit, cursor], ); - return res.map(SpotubeArtistObject.fromJson).toList(); + return SpotubePaginationResponseObject.fromJson( + res, + SpotubeArtistObject.fromJson, + ); } - Future> listUserSavedArtists( + Future> + listUserSavedArtists( String userId, { - int limit = defaultMetadataLimit, - int offset = defaultMetadataOffset, + String limit = defaultMetadataLimit, + String? cursor, }) async { final res = await invoke( "metadataApi.listUserSavedArtists", - [userId, limit, offset], + [userId, limit, cursor], ); - return res.map(SpotubeArtistObject.fromJson).toList(); + return SpotubePaginationResponseObject.fromJson( + res, + SpotubeArtistObject.fromJson, + ); } // ----- Search ------ Future search( String query, { - int limit = defaultMetadataLimit, - int offset = defaultMetadataOffset, + String limit = defaultMetadataLimit, + String? cursor, }) async { final res = await invoke( "metadataApi.search", - [query, limit, offset], + [query, limit, cursor], ); - return res.map(SpotubeSearchResponseObject.fromJson).toList(); + return SpotubeSearchResponseObject.fromJson(res); + } + + // ----- Feed ------ + Future getFeed(String id) async { + final res = await invoke("metadataApi.getFeed", [id]); + + return SpotubeFeedObject.fromJson(res); + } + + Future> listFeeds({ + String limit = defaultMetadataLimit, + String? cursor, + }) async { + final res = await invoke("metadataApi.listFeeds", [limit, cursor]); + + return SpotubePaginationResponseObject.fromJson( + res, + SpotubeFeedObject.fromJson, + ); } // ----- User ------