feat: add track endpoint for metadata service

This commit is contained in:
Kingkor Roy Tirtho 2025-06-16 14:09:20 +06:00
parent fcc05a4243
commit b979a6ede9
8 changed files with 540 additions and 203 deletions

View File

@ -105,6 +105,16 @@ abstract class FakeData {
..explicit = false
..linkedFrom = trackLink;
static final simpleTrack = SpotubeSimpleTrackObject(
id: "1",
name: "A Track Name",
artists: [],
album: albumSimple,
externalUri: "https://example.com",
durationMs: 50000,
explicit: false,
);
static final TrackLink trackLink = TrackLink()
..id = "1"
..type = "type"

View File

@ -1,36 +1,31 @@
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/provider/scrobbler/scrobbler.dart';
import 'package:spotube/provider/spotify/spotify.dart';
import 'package:spotube/models/metadata/metadata.dart';
import 'package:spotube/provider/metadata_plugin/library/tracks.dart';
typedef UseTrackToggleLike = ({
bool isLiked,
Future<void> Function(Track track) toggleTrackLike,
Future<void> Function(SpotubeTrackObject track) toggleTrackLike,
});
UseTrackToggleLike useTrackToggleLike(Track track, WidgetRef ref) {
final savedTracks = ref.watch(likedTracksProvider);
final savedTracksNotifier = ref.watch(likedTracksProvider.notifier);
UseTrackToggleLike useTrackToggleLike(SpotubeTrackObject track, WidgetRef ref) {
final savedTracksNotifier =
ref.watch(metadataPluginSavedTracksProvider.notifier);
final isLiked = useMemoized(
() =>
savedTracks.asData?.value.any((element) => element.id == track.id) ??
false,
[savedTracks.asData?.value, track.id],
final isSavedTrack = ref.watch(
metadataPluginIsSavedTrackProvider(track.id),
);
final scrobblerNotifier = ref.read(scrobblerProvider.notifier);
return (
isLiked: isLiked,
isLiked: isSavedTrack.asData?.value ?? false,
toggleTrackLike: (track) async {
await savedTracksNotifier.toggleFavorite(track);
final isLikedTrack = await ref.read(
metadataPluginIsSavedTrackProvider(track.id).future,
);
if (!isLiked) {
await scrobblerNotifier.love(track);
if (isLikedTrack) {
await savedTracksNotifier.removeFavorite([track]);
} else {
await scrobblerNotifier.unlove(track);
await savedTracksNotifier.addFavorite([track]);
}
},
);

View File

@ -2820,38 +2820,134 @@ abstract class _SpotubeSearchResponseObject
get copyWith => throw _privateConstructorUsedError;
}
SpotubeFullTrackObject _$SpotubeFullTrackObjectFromJson(
Map<String, dynamic> json) {
return _SpotubeFullTrackObject.fromJson(json);
SpotubeTrackObject _$SpotubeTrackObjectFromJson(Map<String, dynamic> json) {
switch (json['runtimeType']) {
case 'full':
return SpotubeFullTrackObject.fromJson(json);
case 'simple':
return SpotubeSimpleTrackObject.fromJson(json);
default:
throw CheckedFromJsonException(json, 'runtimeType', 'SpotubeTrackObject',
'Invalid union type "${json['runtimeType']}"!');
}
}
/// @nodoc
mixin _$SpotubeFullTrackObject {
mixin _$SpotubeTrackObject {
String get id => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
String get externalUri => throw _privateConstructorUsedError;
List<SpotubeSimpleArtistObject> get artists =>
throw _privateConstructorUsedError;
SpotubeSimpleAlbumObject get album => throw _privateConstructorUsedError;
SpotubeSimpleAlbumObject? get album => throw _privateConstructorUsedError;
int get durationMs => throw _privateConstructorUsedError;
String get isrc => throw _privateConstructorUsedError;
bool get explicit => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(
String id,
String name,
String externalUri,
List<SpotubeSimpleArtistObject> artists,
SpotubeSimpleAlbumObject album,
int durationMs,
String isrc,
bool explicit)
full,
required TResult Function(
String id,
String name,
String externalUri,
int durationMs,
bool explicit,
List<SpotubeSimpleArtistObject> artists,
SpotubeSimpleAlbumObject? album)
simple,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(
String id,
String name,
String externalUri,
List<SpotubeSimpleArtistObject> artists,
SpotubeSimpleAlbumObject album,
int durationMs,
String isrc,
bool explicit)?
full,
TResult? Function(
String id,
String name,
String externalUri,
int durationMs,
bool explicit,
List<SpotubeSimpleArtistObject> artists,
SpotubeSimpleAlbumObject? album)?
simple,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(
String id,
String name,
String externalUri,
List<SpotubeSimpleArtistObject> artists,
SpotubeSimpleAlbumObject album,
int durationMs,
String isrc,
bool explicit)?
full,
TResult Function(
String id,
String name,
String externalUri,
int durationMs,
bool explicit,
List<SpotubeSimpleArtistObject> artists,
SpotubeSimpleAlbumObject? album)?
simple,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(SpotubeFullTrackObject value) full,
required TResult Function(SpotubeSimpleTrackObject value) simple,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(SpotubeFullTrackObject value)? full,
TResult? Function(SpotubeSimpleTrackObject value)? simple,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(SpotubeFullTrackObject value)? full,
TResult Function(SpotubeSimpleTrackObject value)? simple,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
/// Serializes this SpotubeFullTrackObject to a JSON map.
/// Serializes this SpotubeTrackObject to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of SpotubeFullTrackObject
/// Create a copy of SpotubeTrackObject
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$SpotubeFullTrackObjectCopyWith<SpotubeFullTrackObject> get copyWith =>
$SpotubeTrackObjectCopyWith<SpotubeTrackObject> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $SpotubeFullTrackObjectCopyWith<$Res> {
factory $SpotubeFullTrackObjectCopyWith(SpotubeFullTrackObject value,
$Res Function(SpotubeFullTrackObject) then) =
_$SpotubeFullTrackObjectCopyWithImpl<$Res, SpotubeFullTrackObject>;
abstract class $SpotubeTrackObjectCopyWith<$Res> {
factory $SpotubeTrackObjectCopyWith(
SpotubeTrackObject value, $Res Function(SpotubeTrackObject) then) =
_$SpotubeTrackObjectCopyWithImpl<$Res, SpotubeTrackObject>;
@useResult
$Res call(
{String id,
@ -2860,24 +2956,22 @@ abstract class $SpotubeFullTrackObjectCopyWith<$Res> {
List<SpotubeSimpleArtistObject> artists,
SpotubeSimpleAlbumObject album,
int durationMs,
String isrc,
bool explicit});
$SpotubeSimpleAlbumObjectCopyWith<$Res> get album;
$SpotubeSimpleAlbumObjectCopyWith<$Res>? get album;
}
/// @nodoc
class _$SpotubeFullTrackObjectCopyWithImpl<$Res,
$Val extends SpotubeFullTrackObject>
implements $SpotubeFullTrackObjectCopyWith<$Res> {
_$SpotubeFullTrackObjectCopyWithImpl(this._value, this._then);
class _$SpotubeTrackObjectCopyWithImpl<$Res, $Val extends SpotubeTrackObject>
implements $SpotubeTrackObjectCopyWith<$Res> {
_$SpotubeTrackObjectCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of SpotubeFullTrackObject
/// Create a copy of SpotubeTrackObject
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
@ -2888,7 +2982,6 @@ class _$SpotubeFullTrackObjectCopyWithImpl<$Res,
Object? artists = null,
Object? album = null,
Object? durationMs = null,
Object? isrc = null,
Object? explicit = null,
}) {
return _then(_value.copyWith(
@ -2909,17 +3002,13 @@ class _$SpotubeFullTrackObjectCopyWithImpl<$Res,
: artists // ignore: cast_nullable_to_non_nullable
as List<SpotubeSimpleArtistObject>,
album: null == album
? _value.album
? _value.album!
: album // ignore: cast_nullable_to_non_nullable
as SpotubeSimpleAlbumObject,
durationMs: null == durationMs
? _value.durationMs
: durationMs // ignore: cast_nullable_to_non_nullable
as int,
isrc: null == isrc
? _value.isrc
: isrc // ignore: cast_nullable_to_non_nullable
as String,
explicit: null == explicit
? _value.explicit
: explicit // ignore: cast_nullable_to_non_nullable
@ -2927,12 +3016,16 @@ class _$SpotubeFullTrackObjectCopyWithImpl<$Res,
) as $Val);
}
/// Create a copy of SpotubeFullTrackObject
/// Create a copy of SpotubeTrackObject
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SpotubeSimpleAlbumObjectCopyWith<$Res> get album {
return $SpotubeSimpleAlbumObjectCopyWith<$Res>(_value.album, (value) {
$SpotubeSimpleAlbumObjectCopyWith<$Res>? get album {
if (_value.album == null) {
return null;
}
return $SpotubeSimpleAlbumObjectCopyWith<$Res>(_value.album!, (value) {
return _then(_value.copyWith(album: value) as $Val);
});
}
@ -2940,7 +3033,7 @@ class _$SpotubeFullTrackObjectCopyWithImpl<$Res,
/// @nodoc
abstract class _$$SpotubeFullTrackObjectImplCopyWith<$Res>
implements $SpotubeFullTrackObjectCopyWith<$Res> {
implements $SpotubeTrackObjectCopyWith<$Res> {
factory _$$SpotubeFullTrackObjectImplCopyWith(
_$SpotubeFullTrackObjectImpl value,
$Res Function(_$SpotubeFullTrackObjectImpl) then) =
@ -2963,15 +3056,14 @@ abstract class _$$SpotubeFullTrackObjectImplCopyWith<$Res>
/// @nodoc
class __$$SpotubeFullTrackObjectImplCopyWithImpl<$Res>
extends _$SpotubeFullTrackObjectCopyWithImpl<$Res,
_$SpotubeFullTrackObjectImpl>
extends _$SpotubeTrackObjectCopyWithImpl<$Res, _$SpotubeFullTrackObjectImpl>
implements _$$SpotubeFullTrackObjectImplCopyWith<$Res> {
__$$SpotubeFullTrackObjectImplCopyWithImpl(
_$SpotubeFullTrackObjectImpl _value,
$Res Function(_$SpotubeFullTrackObjectImpl) _then)
: super(_value, _then);
/// Create a copy of SpotubeFullTrackObject
/// Create a copy of SpotubeTrackObject
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
@ -3020,11 +3112,21 @@ class __$$SpotubeFullTrackObjectImplCopyWithImpl<$Res>
as bool,
));
}
/// Create a copy of SpotubeTrackObject
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SpotubeSimpleAlbumObjectCopyWith<$Res> get album {
return $SpotubeSimpleAlbumObjectCopyWith<$Res>(_value.album, (value) {
return _then(_value.copyWith(album: value));
});
}
}
/// @nodoc
@JsonSerializable()
class _$SpotubeFullTrackObjectImpl implements _SpotubeFullTrackObject {
class _$SpotubeFullTrackObjectImpl implements SpotubeFullTrackObject {
_$SpotubeFullTrackObjectImpl(
{required this.id,
required this.name,
@ -3033,8 +3135,10 @@ class _$SpotubeFullTrackObjectImpl implements _SpotubeFullTrackObject {
required this.album,
required this.durationMs,
required this.isrc,
required this.explicit})
: _artists = artists;
required this.explicit,
final String? $type})
: _artists = artists,
$type = $type ?? 'full';
factory _$SpotubeFullTrackObjectImpl.fromJson(Map<String, dynamic> json) =>
_$$SpotubeFullTrackObjectImplFromJson(json);
@ -3063,9 +3167,12 @@ class _$SpotubeFullTrackObjectImpl implements _SpotubeFullTrackObject {
@override
final bool explicit;
@JsonKey(name: 'runtimeType')
final String $type;
@override
String toString() {
return 'SpotubeFullTrackObject(id: $id, name: $name, externalUri: $externalUri, artists: $artists, album: $album, durationMs: $durationMs, isrc: $isrc, explicit: $explicit)';
return 'SpotubeTrackObject.full(id: $id, name: $name, externalUri: $externalUri, artists: $artists, album: $album, durationMs: $durationMs, isrc: $isrc, explicit: $explicit)';
}
@override
@ -3099,7 +3206,7 @@ class _$SpotubeFullTrackObjectImpl implements _SpotubeFullTrackObject {
isrc,
explicit);
/// Create a copy of SpotubeFullTrackObject
/// Create a copy of SpotubeTrackObject
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@ -3108,6 +3215,122 @@ class _$SpotubeFullTrackObjectImpl implements _SpotubeFullTrackObject {
get copyWith => __$$SpotubeFullTrackObjectImplCopyWithImpl<
_$SpotubeFullTrackObjectImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(
String id,
String name,
String externalUri,
List<SpotubeSimpleArtistObject> artists,
SpotubeSimpleAlbumObject album,
int durationMs,
String isrc,
bool explicit)
full,
required TResult Function(
String id,
String name,
String externalUri,
int durationMs,
bool explicit,
List<SpotubeSimpleArtistObject> artists,
SpotubeSimpleAlbumObject? album)
simple,
}) {
return full(
id, name, externalUri, artists, album, durationMs, isrc, explicit);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(
String id,
String name,
String externalUri,
List<SpotubeSimpleArtistObject> artists,
SpotubeSimpleAlbumObject album,
int durationMs,
String isrc,
bool explicit)?
full,
TResult? Function(
String id,
String name,
String externalUri,
int durationMs,
bool explicit,
List<SpotubeSimpleArtistObject> artists,
SpotubeSimpleAlbumObject? album)?
simple,
}) {
return full?.call(
id, name, externalUri, artists, album, durationMs, isrc, explicit);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(
String id,
String name,
String externalUri,
List<SpotubeSimpleArtistObject> artists,
SpotubeSimpleAlbumObject album,
int durationMs,
String isrc,
bool explicit)?
full,
TResult Function(
String id,
String name,
String externalUri,
int durationMs,
bool explicit,
List<SpotubeSimpleArtistObject> artists,
SpotubeSimpleAlbumObject? album)?
simple,
required TResult orElse(),
}) {
if (full != null) {
return full(
id, name, externalUri, artists, album, durationMs, isrc, explicit);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(SpotubeFullTrackObject value) full,
required TResult Function(SpotubeSimpleTrackObject value) simple,
}) {
return full(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(SpotubeFullTrackObject value)? full,
TResult? Function(SpotubeSimpleTrackObject value)? simple,
}) {
return full?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(SpotubeFullTrackObject value)? full,
TResult Function(SpotubeSimpleTrackObject value)? simple,
required TResult orElse(),
}) {
if (full != null) {
return full(this);
}
return orElse();
}
@override
Map<String, dynamic> toJson() {
return _$$SpotubeFullTrackObjectImplToJson(
@ -3116,8 +3339,8 @@ class _$SpotubeFullTrackObjectImpl implements _SpotubeFullTrackObject {
}
}
abstract class _SpotubeFullTrackObject implements SpotubeFullTrackObject {
factory _SpotubeFullTrackObject(
abstract class SpotubeFullTrackObject implements SpotubeTrackObject {
factory SpotubeFullTrackObject(
{required final String id,
required final String name,
required final String externalUri,
@ -3127,7 +3350,7 @@ abstract class _SpotubeFullTrackObject implements SpotubeFullTrackObject {
required final String isrc,
required final bool explicit}) = _$SpotubeFullTrackObjectImpl;
factory _SpotubeFullTrackObject.fromJson(Map<String, dynamic> json) =
factory SpotubeFullTrackObject.fromJson(Map<String, dynamic> json) =
_$SpotubeFullTrackObjectImpl.fromJson;
@override
@ -3142,12 +3365,11 @@ abstract class _SpotubeFullTrackObject implements SpotubeFullTrackObject {
SpotubeSimpleAlbumObject get album;
@override
int get durationMs;
@override
String get isrc;
@override
bool get explicit;
/// Create a copy of SpotubeFullTrackObject
/// Create a copy of SpotubeTrackObject
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@ -3155,124 +3377,9 @@ abstract class _SpotubeFullTrackObject implements SpotubeFullTrackObject {
get copyWith => throw _privateConstructorUsedError;
}
SpotubeSimpleTrackObject _$SpotubeSimpleTrackObjectFromJson(
Map<String, dynamic> json) {
return _SpotubeSimpleTrackObject.fromJson(json);
}
/// @nodoc
mixin _$SpotubeSimpleTrackObject {
String get id => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
String get externalUri => throw _privateConstructorUsedError;
int get durationMs => throw _privateConstructorUsedError;
bool get explicit => throw _privateConstructorUsedError;
List<SpotubeSimpleArtistObject> get artists =>
throw _privateConstructorUsedError;
SpotubeSimpleAlbumObject? get album => throw _privateConstructorUsedError;
/// Serializes this SpotubeSimpleTrackObject to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of SpotubeSimpleTrackObject
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$SpotubeSimpleTrackObjectCopyWith<SpotubeSimpleTrackObject> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $SpotubeSimpleTrackObjectCopyWith<$Res> {
factory $SpotubeSimpleTrackObjectCopyWith(SpotubeSimpleTrackObject value,
$Res Function(SpotubeSimpleTrackObject) then) =
_$SpotubeSimpleTrackObjectCopyWithImpl<$Res, SpotubeSimpleTrackObject>;
@useResult
$Res call(
{String id,
String name,
String externalUri,
int durationMs,
bool explicit,
List<SpotubeSimpleArtistObject> artists,
SpotubeSimpleAlbumObject? album});
$SpotubeSimpleAlbumObjectCopyWith<$Res>? get album;
}
/// @nodoc
class _$SpotubeSimpleTrackObjectCopyWithImpl<$Res,
$Val extends SpotubeSimpleTrackObject>
implements $SpotubeSimpleTrackObjectCopyWith<$Res> {
_$SpotubeSimpleTrackObjectCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of SpotubeSimpleTrackObject
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? name = null,
Object? externalUri = null,
Object? durationMs = null,
Object? explicit = null,
Object? artists = null,
Object? album = freezed,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
externalUri: null == externalUri
? _value.externalUri
: externalUri // ignore: cast_nullable_to_non_nullable
as String,
durationMs: null == durationMs
? _value.durationMs
: durationMs // ignore: cast_nullable_to_non_nullable
as int,
explicit: null == explicit
? _value.explicit
: explicit // ignore: cast_nullable_to_non_nullable
as bool,
artists: null == artists
? _value.artists
: artists // ignore: cast_nullable_to_non_nullable
as List<SpotubeSimpleArtistObject>,
album: freezed == album
? _value.album
: album // ignore: cast_nullable_to_non_nullable
as SpotubeSimpleAlbumObject?,
) as $Val);
}
/// Create a copy of SpotubeSimpleTrackObject
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SpotubeSimpleAlbumObjectCopyWith<$Res>? get album {
if (_value.album == null) {
return null;
}
return $SpotubeSimpleAlbumObjectCopyWith<$Res>(_value.album!, (value) {
return _then(_value.copyWith(album: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$SpotubeSimpleTrackObjectImplCopyWith<$Res>
implements $SpotubeSimpleTrackObjectCopyWith<$Res> {
implements $SpotubeTrackObjectCopyWith<$Res> {
factory _$$SpotubeSimpleTrackObjectImplCopyWith(
_$SpotubeSimpleTrackObjectImpl value,
$Res Function(_$SpotubeSimpleTrackObjectImpl) then) =
@ -3294,7 +3401,7 @@ abstract class _$$SpotubeSimpleTrackObjectImplCopyWith<$Res>
/// @nodoc
class __$$SpotubeSimpleTrackObjectImplCopyWithImpl<$Res>
extends _$SpotubeSimpleTrackObjectCopyWithImpl<$Res,
extends _$SpotubeTrackObjectCopyWithImpl<$Res,
_$SpotubeSimpleTrackObjectImpl>
implements _$$SpotubeSimpleTrackObjectImplCopyWith<$Res> {
__$$SpotubeSimpleTrackObjectImplCopyWithImpl(
@ -3302,7 +3409,7 @@ class __$$SpotubeSimpleTrackObjectImplCopyWithImpl<$Res>
$Res Function(_$SpotubeSimpleTrackObjectImpl) _then)
: super(_value, _then);
/// Create a copy of SpotubeSimpleTrackObject
/// Create a copy of SpotubeTrackObject
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
@ -3350,7 +3457,7 @@ class __$$SpotubeSimpleTrackObjectImplCopyWithImpl<$Res>
/// @nodoc
@JsonSerializable()
class _$SpotubeSimpleTrackObjectImpl implements _SpotubeSimpleTrackObject {
class _$SpotubeSimpleTrackObjectImpl implements SpotubeSimpleTrackObject {
_$SpotubeSimpleTrackObjectImpl(
{required this.id,
required this.name,
@ -3358,8 +3465,10 @@ class _$SpotubeSimpleTrackObjectImpl implements _SpotubeSimpleTrackObject {
required this.durationMs,
required this.explicit,
final List<SpotubeSimpleArtistObject> artists = const [],
this.album})
: _artists = artists;
this.album,
final String? $type})
: _artists = artists,
$type = $type ?? 'simple';
factory _$SpotubeSimpleTrackObjectImpl.fromJson(Map<String, dynamic> json) =>
_$$SpotubeSimpleTrackObjectImplFromJson(json);
@ -3386,9 +3495,12 @@ class _$SpotubeSimpleTrackObjectImpl implements _SpotubeSimpleTrackObject {
@override
final SpotubeSimpleAlbumObject? album;
@JsonKey(name: 'runtimeType')
final String $type;
@override
String toString() {
return 'SpotubeSimpleTrackObject(id: $id, name: $name, externalUri: $externalUri, durationMs: $durationMs, explicit: $explicit, artists: $artists, album: $album)';
return 'SpotubeTrackObject.simple(id: $id, name: $name, externalUri: $externalUri, durationMs: $durationMs, explicit: $explicit, artists: $artists, album: $album)';
}
@override
@ -3420,7 +3532,7 @@ class _$SpotubeSimpleTrackObjectImpl implements _SpotubeSimpleTrackObject {
const DeepCollectionEquality().hash(_artists),
album);
/// Create a copy of SpotubeSimpleTrackObject
/// Create a copy of SpotubeTrackObject
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@ -3429,6 +3541,121 @@ class _$SpotubeSimpleTrackObjectImpl implements _SpotubeSimpleTrackObject {
get copyWith => __$$SpotubeSimpleTrackObjectImplCopyWithImpl<
_$SpotubeSimpleTrackObjectImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(
String id,
String name,
String externalUri,
List<SpotubeSimpleArtistObject> artists,
SpotubeSimpleAlbumObject album,
int durationMs,
String isrc,
bool explicit)
full,
required TResult Function(
String id,
String name,
String externalUri,
int durationMs,
bool explicit,
List<SpotubeSimpleArtistObject> artists,
SpotubeSimpleAlbumObject? album)
simple,
}) {
return simple(id, name, externalUri, durationMs, explicit, artists, album);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(
String id,
String name,
String externalUri,
List<SpotubeSimpleArtistObject> artists,
SpotubeSimpleAlbumObject album,
int durationMs,
String isrc,
bool explicit)?
full,
TResult? Function(
String id,
String name,
String externalUri,
int durationMs,
bool explicit,
List<SpotubeSimpleArtistObject> artists,
SpotubeSimpleAlbumObject? album)?
simple,
}) {
return simple?.call(
id, name, externalUri, durationMs, explicit, artists, album);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(
String id,
String name,
String externalUri,
List<SpotubeSimpleArtistObject> artists,
SpotubeSimpleAlbumObject album,
int durationMs,
String isrc,
bool explicit)?
full,
TResult Function(
String id,
String name,
String externalUri,
int durationMs,
bool explicit,
List<SpotubeSimpleArtistObject> artists,
SpotubeSimpleAlbumObject? album)?
simple,
required TResult orElse(),
}) {
if (simple != null) {
return simple(
id, name, externalUri, durationMs, explicit, artists, album);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(SpotubeFullTrackObject value) full,
required TResult Function(SpotubeSimpleTrackObject value) simple,
}) {
return simple(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(SpotubeFullTrackObject value)? full,
TResult? Function(SpotubeSimpleTrackObject value)? simple,
}) {
return simple?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(SpotubeFullTrackObject value)? full,
TResult Function(SpotubeSimpleTrackObject value)? simple,
required TResult orElse(),
}) {
if (simple != null) {
return simple(this);
}
return orElse();
}
@override
Map<String, dynamic> toJson() {
return _$$SpotubeSimpleTrackObjectImplToJson(
@ -3437,8 +3664,8 @@ class _$SpotubeSimpleTrackObjectImpl implements _SpotubeSimpleTrackObject {
}
}
abstract class _SpotubeSimpleTrackObject implements SpotubeSimpleTrackObject {
factory _SpotubeSimpleTrackObject(
abstract class SpotubeSimpleTrackObject implements SpotubeTrackObject {
factory SpotubeSimpleTrackObject(
{required final String id,
required final String name,
required final String externalUri,
@ -3447,7 +3674,7 @@ abstract class _SpotubeSimpleTrackObject implements SpotubeSimpleTrackObject {
final List<SpotubeSimpleArtistObject> artists,
final SpotubeSimpleAlbumObject? album}) = _$SpotubeSimpleTrackObjectImpl;
factory _SpotubeSimpleTrackObject.fromJson(Map<String, dynamic> json) =
factory SpotubeSimpleTrackObject.fromJson(Map<String, dynamic> json) =
_$SpotubeSimpleTrackObjectImpl.fromJson;
@override
@ -3465,7 +3692,7 @@ abstract class _SpotubeSimpleTrackObject implements SpotubeSimpleTrackObject {
@override
SpotubeSimpleAlbumObject? get album;
/// Create a copy of SpotubeSimpleTrackObject
/// Create a copy of SpotubeTrackObject
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)

View File

@ -302,6 +302,7 @@ _$SpotubeFullTrackObjectImpl _$$SpotubeFullTrackObjectImplFromJson(Map json) =>
durationMs: (json['durationMs'] as num).toInt(),
isrc: json['isrc'] as String,
explicit: json['explicit'] as bool,
$type: json['runtimeType'] as String?,
);
Map<String, dynamic> _$$SpotubeFullTrackObjectImplToJson(
@ -315,6 +316,7 @@ Map<String, dynamic> _$$SpotubeFullTrackObjectImplToJson(
'durationMs': instance.durationMs,
'isrc': instance.isrc,
'explicit': instance.explicit,
'runtimeType': instance.$type,
};
_$SpotubeSimpleTrackObjectImpl _$$SpotubeSimpleTrackObjectImplFromJson(
@ -334,6 +336,7 @@ _$SpotubeSimpleTrackObjectImpl _$$SpotubeSimpleTrackObjectImplFromJson(
? null
: SpotubeSimpleAlbumObject.fromJson(
Map<String, dynamic>.from(json['album'] as Map)),
$type: json['runtimeType'] as String?,
);
Map<String, dynamic> _$$SpotubeSimpleTrackObjectImplToJson(
@ -346,6 +349,7 @@ Map<String, dynamic> _$$SpotubeSimpleTrackObjectImplToJson(
'explicit': instance.explicit,
'artists': instance.artists.map((e) => e.toJson()).toList(),
'album': instance.album?.toJson(),
'runtimeType': instance.$type,
};
_$SpotubeUserObjectImpl _$$SpotubeUserObjectImplFromJson(Map json) =>

View File

@ -1,8 +1,8 @@
part of 'metadata.dart';
@freezed
class SpotubeFullTrackObject with _$SpotubeFullTrackObject {
factory SpotubeFullTrackObject({
class SpotubeTrackObject with _$SpotubeTrackObject {
factory SpotubeTrackObject.full({
required String id,
required String name,
required String externalUri,
@ -11,15 +11,9 @@ class SpotubeFullTrackObject with _$SpotubeFullTrackObject {
required int durationMs,
required String isrc,
required bool explicit,
}) = _SpotubeFullTrackObject;
}) = SpotubeFullTrackObject;
factory SpotubeFullTrackObject.fromJson(Map<String, dynamic> json) =>
_$SpotubeFullTrackObjectFromJson(json);
}
@freezed
class SpotubeSimpleTrackObject with _$SpotubeSimpleTrackObject {
factory SpotubeSimpleTrackObject({
factory SpotubeTrackObject.simple({
required String id,
required String name,
required String externalUri,
@ -27,8 +21,12 @@ class SpotubeSimpleTrackObject with _$SpotubeSimpleTrackObject {
required bool explicit,
@Default([]) List<SpotubeSimpleArtistObject> artists,
SpotubeSimpleAlbumObject? album,
}) = _SpotubeSimpleTrackObject;
}) = SpotubeSimpleTrackObject;
factory SpotubeSimpleTrackObject.fromJson(Map<String, dynamic> json) =>
_$SpotubeSimpleTrackObjectFromJson(json);
factory SpotubeTrackObject.fromJson(Map<String, dynamic> json) =>
_$SpotubeTrackObjectFromJson(
json.containsKey("isrc")
? {...json, "runtimeType": "full"}
: {...json, "runtimeType": "simple"},
);
}

View File

@ -0,0 +1,71 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/models/metadata/metadata.dart';
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
import 'package:spotube/provider/metadata_plugin/user.dart';
import 'package:spotube/provider/metadata_plugin/utils/common.dart';
import 'package:spotube/provider/metadata_plugin/utils/paginated.dart';
class MetadataPluginSavedTracksNotifier
extends AutoDisposePaginatedAsyncNotifier<SpotubeFullTrackObject> {
MetadataPluginSavedTracksNotifier() : super();
@override
fetch(offset, limit) async {
final user = await ref.read(metadataPluginUserProvider.future);
if (user == null) {
throw Exception(
'User not found \n'
'You need to be logged in to access saved tracks.',
);
}
final tracks = await (await metadataPlugin).album.tracks(
user.id,
offset: offset,
limit: limit,
);
return tracks;
}
@override
build() async {
ref.cacheFor();
ref.watch(metadataPluginProvider);
return await fetch(0, 20);
}
Future<void> addFavorite(List<SpotubeTrackObject> tracks) async {
await (await metadataPlugin).track.save(tracks.map((e) => e.id).toList());
for (final track in tracks) {
ref.invalidate(metadataPluginIsSavedTrackProvider(track.id));
}
}
Future<void> removeFavorite(List<SpotubeTrackObject> tracks) async {
await (await metadataPlugin).track.unsave(tracks.map((e) => e.id).toList());
for (final track in tracks) {
ref.invalidate(metadataPluginIsSavedTrackProvider(track.id));
}
}
}
final metadataPluginSavedTracksProvider = AutoDisposeAsyncNotifierProvider<
MetadataPluginSavedTracksNotifier,
SpotubePaginationResponseObject<SpotubeFullTrackObject>>(
() => MetadataPluginSavedTracksNotifier(),
);
final metadataPluginIsSavedTrackProvider =
FutureProvider.autoDispose.family<bool, String>(
(ref, trackId) async {
final metadataPlugin = await ref.watch(metadataPluginProvider.future);
return metadataPlugin!.user
.isSavedTracks([trackId]).then((value) => value.first);
},
);

View File

@ -0,0 +1,29 @@
import 'package:hetu_script/hetu_script.dart';
import 'package:hetu_script/values.dart';
import 'package:spotube/models/metadata/metadata.dart';
class MetadataPluginTrackEndpoint {
final Hetu hetu;
MetadataPluginTrackEndpoint(this.hetu);
HTInstance get hetuMetadataTrack =>
(hetu.fetch("metadataPlugin") as HTInstance).memberGet("track")
as HTInstance;
Future<SpotubeFullTrackObject> getTrack(String id) async {
final raw =
await hetuMetadataTrack.invoke("getTrack", positionalArgs: [id]) as Map;
return SpotubeFullTrackObject.fromJson(
raw.cast<String, dynamic>(),
);
}
Future<void> save(List<String> ids) async {
await hetuMetadataTrack.invoke("save", positionalArgs: [ids]);
}
Future<void> unsave(List<String> ids) async {
await hetuMetadataTrack.invoke("unsave", positionalArgs: [ids]);
}
}

View File

@ -17,6 +17,7 @@ import 'package:spotube/services/metadata/endpoints/auth.dart';
import 'package:spotube/services/metadata/endpoints/browse.dart';
import 'package:spotube/services/metadata/endpoints/playlist.dart';
import 'package:spotube/services/metadata/endpoints/search.dart';
import 'package:spotube/services/metadata/endpoints/track.dart';
import 'package:spotube/services/metadata/endpoints/user.dart';
const defaultMetadataLimit = "20";
@ -83,6 +84,7 @@ class MetadataPlugin {
late final MetadataPluginBrowseEndpoint browse;
late final MetadataPluginSearchEndpoint search;
late final MetadataPluginPlaylistEndpoint playlist;
late final MetadataPluginTrackEndpoint track;
late final MetadataPluginUserEndpoint user;
MetadataPlugin._(this.hetu) {
@ -93,6 +95,7 @@ class MetadataPlugin {
browse = MetadataPluginBrowseEndpoint(hetu);
search = MetadataPluginSearchEndpoint(hetu);
playlist = MetadataPluginPlaylistEndpoint(hetu);
track = MetadataPluginTrackEndpoint(hetu);
user = MetadataPluginUserEndpoint(hetu);
}
}